<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="application-name" content="JSON SCADA Tabular/Alarms Viewer">
<meta name="description" content="Tabular/Alarms Viewer">
<meta name="author" content="Ricardo L. Olsen">
<meta name="viewport" content="width=850, user-scalable=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<title>Tabular/Alarms Viewer</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />    
<link rel="apple-touch-icon" href="images/tabular.png" />
<style>
@font-face {
  font-family: 'Source Sans Pro';
  font-style: normal;
  font-weight: 400;
  src: local('Source Sans Pro'), local('SourceSansPro-Regular'), url(lib/SourceSansPro-Regular.woff2) format('woff2');
}
</style>
<script src="util.js"></script>
<script src="lib/jquery-1.8.3.js"></script>
<script src="lib/core-1.0.js"></script>
<script src="lib/shortcut-2.01b.js"></script>
<script src="lib/chroma.js"></script>
<script src="fan.js"></script>
<script src="legacy_options.js"></script>
<script src="../i18n/messages_i18n.js"></script>
<script src="config_viewers_default.js"></script>
<script src="../conf/config_viewers.js"></script>
<script src="pntserver.js"></script>
<script src="images.js"></script>
<script src="opc-codes.js"></script>
<script>
"use strict";

// This Viewer presents data in a grid, it also acts as Alarms Viewer.
//
// {json:scada} - Copyright 2020-2023 - Ricardo L. Olsen
// Derived from: OSHMI/Open Substation HMI - Copyright 2008-2021 - Ricardo L. Olsen

var L = []; // Event list
var V = []; // Point values
var S = []; // Point values as String
var F = []; // Point quality flags
var T = []; // Alarm time tags
var TAGS = []; // Point tag names
var NPTS = []; // Point numbers by tags names
var SUBS = []; // Grouping level 1 of points (e.g. substation)
var BAYS = []; // Grouping level 2 of points (e.g. bay)
var DCRS = []; // Point description
var ANOTS = []; // Point blocking annotation
var DNOTES = []; // Point documental annotations
var STONS = []; // On status texts
var STOFS = []; // Off status texts
var STONS = []; // On status texts
var STOFS = []; // Off status texts
var LIMSUPS = []; // Analog superior limits for points
var LIMINFS = []; // Analog inferior limits for points
var INVTAGS = []; // List of invalid tags (not found)
var NUM_VAR = 0;        // aqui o servidor informa o número de variações digitais
var NUM_VAR_ANT = 0;    // estado anterior da variável NUM_VAR
var GROUP1_LIST = []; // list of possible group1 values
var ALARMBEEP = 0; // alarm beep status
var ALARMBEEP_TYPE = 0; // alarm beep type
var BEEP_POINTKEY = -1;
var CNTUPDATES_POINTKEY = -2;

// variáveis para o diálogo de comando
var NPTO, ID, ESTACAO, MODULO, DESC, ST_ON, ST_OFF, VAL_STR, CNPTO, CHANDLE, CID, CDESC, CST_ON, CST_OFF;
var LIMS, LIMI, HISTER, ALRIN, ANOT, VLNOR, ESTALM, UNIDADE, SIMULACAO = 0;
var ComandoAck = ''; // texto para confirmação do comando 
var ComandoEnviado = '';

// callback function a ser chamada pelo servidor
var cbf_F = function() {}; 
var toValidId = function(id) {
  return id.replace(/[&\/\\#,+()$~%.'":*?<>{}]/g, '-');
}

var WebSAGE =
{
g_cbBloqAtu: 'cbBloqAtu',
g_timeOutRefresh: 1000 * TabularViewer_RefreshTime,  // tempo de refresh dos dados
g_timeOutReconhece: 4000, // tempo de refresh após reconhecimento total dos alarmes
g_timeOutFalha: 15000, // tempo para falha dos dados, caso servidor não responda 
g_toutID: 0,
g_timerID: 0, 
g_showValsIntervalID: 1,
g_timeoutFalhaID: 0,
g_pass: 0,
g_COL_NPONTO: 0,
g_COL_ID: 1,
g_COL_SUBEST: 2,
g_COL_DESCR: 3,
g_COL_EVENTO: 4,
g_COL_FLAGS: 5,
g_COL_SUPCMD: 6,
g_COL_QUALIF: 7,
g_COL_ESTNOR: 8,
g_COL_DATA: 9,
g_COL_DATA_CAMPO: 10,
g_nponto_sup: 0,
g_nponto_cmd: 0,
g_win_cmd: {},
g_cmd_id: 0,
g_filtroSE: '',
g_filtroMOD: '',
g_muda_mod: 0,
g_win_1stdraw: 0,
g_wait_win: 0,
g_datahoraant: '',
g_waitingServer: 0,
g_cbf_MostraSE: function() {},
g_bloqa: 0, 
g_bloqc: 0, 
g_firstdraw: 1,
g_fontsize: 16,  // tamanho das fontes
g_tbl: {},
g_tblhead: {},
g_titulo_janela: "",
g_filt_all_alarms: "ALARMS_VIEWER",
g_alminfo: [],
g_filterOutList: [],
g_subst_list: [],
g_split_line_code: "®æ",
g_split_mod_descr: " | ",

// Return value from tag or number
getValue: function(tagornumber) {
  return V[tagornumber] || V[NPTS[tagornumber]] || 0;
},

// Return the string value from tag or number
getStringValue: function(tagornumber) {
  return S[tagornumber] || S[NPTS[tagornumber]] || "";
},

// Return flags from tag or number
getFlags: function(tagornumber) {
  var f = F[tagornumber] || F[NPTS[tagornumber]];
  if (isNaN(f))
    return 0xa0 | (WebSAGE.getValue(tagornumber) == 0 ? 0x02 : 0x01);
  else return f;
},

// Return inferior limit from tag or number
getInfLim: function(tagornumber) {
  return LIMINFS[tagornumber] || LIMINFS[NPTS[tagornumber]] || 0;
},

// Return superior limit from tag or number
getSupLim: function(tagornumber) {
  return LIMSUPS[tagornumber] || LIMSUPS[NPTS[tagornumber]] || 0;
},

// Return substation from tag or number
getSubstation: function(tagornumber) {
  return SUBS[tagornumber] || SUBS[NPTS[tagornumber]] || "";
},

// Return bay from tag or number
getBay: function(tagornumber) {
  return BAYS[tagornumber] || BAYS[NPTS[tagornumber]] || "";
},

// Return description from tag or number
getDescription: function(tagornumber) {
  return DCRS[tagornumber] || DCRS[NPTS[tagornumber]] || "";
},

// Return alarm time from tag or number
getTime: function(tagornumber) {
  return T[tagornumber] || T[NPTS[tagornumber]] || "";
},

// Return annotation
getAnnotation: function(tagornumber) {
  return ANOTS[tagornumber] || ANOTS[NPTS[tagornumber]] || "";
},

isAlarmsViewer : function()
{
if ( gup("SELMODULO") == WebSAGE.g_filt_all_alarms ||
     gup("MODE") == WebSAGE.g_filt_all_alarms )
  return true;
return false;
},

  // busca dados do servidor e prepara chamada temporizada de showValsCmd para
  janelaInfo: function(nponto) {
    // faz um bloqueio de 1,5s
    if (WebSAGE.g_travaInfo) {
      return;
    }
    WebSAGE.g_travaInfo = 1;
    setTimeout("WebSAGE.g_travaInfo=0", 1500);

    if (nponto != 0) {
      WebSAGE.g_nponto_sup = nponto;
      WebSAGE.g_cntTentativaInfo = 3;
    } else {
      WebSAGE.g_cntTentativaInfo--;
    }

    if (WebSAGE.g_cntTentativaInfo <= 0) {
      return;
    }

    LIMS = 0;
    LIMI = 0;
    HISTER = 0;
    ALRIN = 0;
    NPTO = 0;
    CNPTO = 0;
    CHANDLE = "";
    ID = "";
    DESC = "";

    if (typeof WebSAGE.g_win_cmd.window == "object")
      if (WebSAGE.g_win_cmd.window) {
        // fecha janela info
        WebSAGE.g_win_cmd.window.close();
      }

    setTimeout(WebSAGE.showValsInfo0, 50);
  },

  // busca dado do ponto tempo real
  showValsInfo0: function() {

    var arrpnt = [WebSAGE.g_nponto_sup];
    WebSAGE.getRealtimeData( arrpnt, true,
          prop => { 
          NPTO = WebSAGE.g_nponto_sup; 
          VAL_STR = S[WebSAGE.g_nponto_sup];
          ESTACAO = SUBS[WebSAGE.g_nponto_sup];
          ST_ON = STONS[WebSAGE.g_nponto_sup];
          ST_OFF = STOFS[WebSAGE.g_nponto_sup];
          UNIDADE = prop.unit;
          DESC = BAYS[WebSAGE.g_nponto_sup] + "-" + DCRS[WebSAGE.g_nponto_sup];
          MODULO = BAYS[WebSAGE.g_nponto_sup];
          LIMS = LIMSUPS[WebSAGE.g_nponto_sup];
          LIMI = LIMINFS[WebSAGE.g_nponto_sup];
          HISTER = prop.hysteresis;
          ALRIN = prop.alarmDisabled;
          CNPTO = prop.commandOfSupervised;
          ID = TAGS[WebSAGE.g_nponto_sup];
          setTimeout(WebSAGE.showValsInfo1, 50);
          }
        );  
  },

  // Abre uma janela popup com dados sobre o ponto
  showValsInfo1: function() {

    // abre nova janela, dá um tempo e vai  preencher os dados da nova janela em outra funcao
    // (para dar tempo de abrir a janela)
    WebSAGE.g_win_1stdraw = 1;
    WebSAGE.g_win_cmd = window.open(
      "dlginfo.html",
      "wsinfo",
      "dependent=yes,height=620,width=400,toolbar=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=no,modal=yes"
    );
    WebSAGE.g_tminfoID = setTimeout("WebSAGE.g_win_cmd.close()", 3000);
    WebSAGE.g_wait_win = 0; // contador para esperar abrir a janela

    // showValsInfo2 será chamado pela própria nova janela aberta em onload
  },

  // Mostra os dados sobre o ponto em janela popup
  showValsInfo2: function() {
    try {
      // test for dialog window opened
      if (
        NPTO === 0 ||
        typeof WebSAGE.g_win_cmd.window !== "object" ||
        WebSAGE.g_win_cmd.window === null ||
        typeof WebSAGE.g_win_cmd.window.closed === "undefined" ||
        WebSAGE.g_win_cmd.window.closed
      ) {
        return; // give up
      }

      // janela carregada
      var se = ESTACAO;
      se = se + "-";

      WebSAGE.writeElemByIdWnd(
        WebSAGE.g_win_cmd,
        "VALOR_SUP",
        roundnum(WebSAGE.getValue(NPTO), 4) + " " + UNIDADE
      );
      WebSAGE.writeElemByIdWnd(
        WebSAGE.g_win_cmd,
        "ESTADO_SUP",
        WebSAGE.getValue(NPTO)==0?ST_ON:ST_OFF
      );

      var SQ = "";
      var Q = WebSAGE.getFlags(NPTO);
/*
      if ((Q & 0x03) == 0x00) {
        WebSAGE.writeElemByIdWnd(
          WebSAGE.g_win_cmd,
          "ESTADO_SUP",
          Msg.QDPIntermed + " (" + Msg.EstadoAtual + ")"
        );
      } else if ((Q & 0x03) == 0x03) {
        WebSAGE.writeElemByIdWnd(
          WebSAGE.g_win_cmd,
          "ESTADO_SUP",
          Msg.QDPInvalido + " (" + Msg.EstadoAtual + ")"
        );
      } else if (V[NPTO] & (0x01 != 0)) {
        WebSAGE.writeElemByIdWnd(
          WebSAGE.g_win_cmd,
          "ESTADO_SUP",
          ST_OFF + " (" + Msg.EstadoAtual + ")"
        );
      } // não zero é off
      else {
        WebSAGE.writeElemByIdWnd(
          WebSAGE.g_win_cmd,
          "ESTADO_SUP",
          ST_ON + " (" + Msg.EstadoAtual + ")"
        );
      } // zero é on
*/

      if (Q & 0x80) {
        SQ += Msg.QFalhado + " ";
      }
      if (Q & 0x10) {
        SQ += Msg.QSubst + " ";
      }
      if ((Q & 0x0c) == 0x04) {
        SQ += Msg.QCalculado + " ";
      } else if ((Q & 0x0c) == 0x0c) {
        SQ += Msg.QManual + " ";
      } else if ((Q & 0x0c) == 0x08) {
        SQ += Msg.QNuncaAtu + " ";
      }
      if (Q & 0x100) {
        SQ += Msg.QAlarmado + " ";
      }
      //if ( Q&0x200 )
      //  SQ += Msg.QAnotacao+' ';
      if (Q & 0x400) {
        SQ += Msg.QAlmInib + " ";
      }
      if (Q & 0x800) {
        SQ += Msg.QNaoNormal + " ";
      }
      if (Q & 0x1000) {
        SQ += Msg.QCongelado + " ";
      }
      if (SQ == "") {
        SQ = Msg.QNormal + " ";
      }

      WebSAGE.writeElemByIdWnd(
        WebSAGE.g_win_cmd,
        "QUALIF",
        Msg.Qualific + ": " + SQ
      );

      if (WebSAGE.g_win_1stdraw) {
        // escreve parâmetros só na primeira vez que abriu a janela
        clearTimeout(WebSAGE.g_tminfoID);
        WebSAGE.g_win_1stdraw = 0;
        WebSAGE.writeElemByIdWnd(WebSAGE.g_win_cmd, "NPONTO_SUP", NPTO + ":" + ID);
        WebSAGE.writeElemByIdWnd(WebSAGE.g_win_cmd, "DESCR_SUP", se + DESC);
        WebSAGE.writeElemByIdWnd(
          WebSAGE.g_win_cmd,
          "SPCMDINTERTRAV",
          Titles.SPCMDINTERTRAV
        );
        WebSAGE.g_win_cmd.document.getElementById("TABULAR").style.display = "";
        //WebSAGE.g_win_cmd.document.getElementById("TABULAR").href="tabular.html?SELMODULO="+ID.substring(0,9);
        Core.addEventListener(
          WebSAGE.g_win_cmd.document.getElementById("TABULAR"),
          "click",
          WebSAGE.tabular
        );

        if (ID.charAt(21) == "M") {
          // Manual não apresenta opção de inibir
          WebSAGE.g_win_cmd.document.getElementById("DIVINIB").style.display =
            "none";
        }

        WebSAGE.g_win_cmd.document.getElementById("CURVAS").style.display = "";
        Core.addEventListener(
          WebSAGE.g_win_cmd.document.getElementById("CURVAS"),
          "click",
          WebSAGE.curvas
        );

        if (Q & 0x20) {
          // mostra parâmetros de limites só para pontos analógicos
          WebSAGE.g_win_cmd.document.getElementById(
            "TENDENCIAS"
          ).style.display = "";
          Core.addEventListener(
            WebSAGE.g_win_cmd.document.getElementById("TENDENCIAS"),
            "click",
            WebSAGE.tendencias
          );

          WebSAGE.g_win_cmd.document.getElementById("VALOR_HID").style.display =
            "";
          WebSAGE.g_win_cmd.document.getElementById("LIMCTRLS").style.display =
            "";
          WebSAGE.g_win_cmd.document.getElementById("LIMSUP").value = LIMS;
          WebSAGE.g_win_cmd.document.getElementById("LIMINF").value = LIMI;
          WebSAGE.g_win_cmd.document.getElementById("HISTER").value = HISTER;
          Core.addEventListener(
            WebSAGE.g_win_cmd.document.getElementById("LIMSUP"),
            "blur",
            WebSAGE.writeProperties
          );
          Core.addEventListener(
            WebSAGE.g_win_cmd.document.getElementById("LIMINF"),
            "blur",
            WebSAGE.writeProperties
          );
          Core.addEventListener(
            WebSAGE.g_win_cmd.document.getElementById("HISTER"),
            "blur",
            WebSAGE.writeProperties
          );
          if (ID.charAt(21) == "M" || SIMULACAO == 1 || SIMULACAO == 2) {
            // permite alterar valor de ponto manual
            WebSAGE.g_win_cmd.document.getElementById(
              "DIVALTVALOR"
            ).style.display = "";
            Core.addEventListener(
              WebSAGE.g_win_cmd.document.getElementById("CBALTVALOR"),
              "click",
              function() {
                WebSAGE.g_win_cmd.document.getElementById(
                  "CBALTVALOR"
                ).style.display = "none";
                WebSAGE.g_win_cmd.document.getElementById(
                  "NOVOVALOR"
                ).style.display = "";
              }
            );
            Core.addEventListener(
              WebSAGE.g_win_cmd.document.getElementById("NOVOVALOR"),
              "blur",
              WebSAGE.writeProperties
            );
          }
        }
        WebSAGE.g_win_cmd.document.getElementById(
          "ANOTACAO"
        ).value = ANOTS[NPTO].replace(/\|\^/g, "\n");
        WebSAGE.g_win_cmd.document.getElementById("CBALRIN").checked =
          ALRIN != "0";
        Core.addEventListener(
          WebSAGE.g_win_cmd.document.getElementById("CBALRIN"),
          "click",
          WebSAGE.writeProperties
        );
        Core.addEventListener(
          WebSAGE.g_win_cmd.document.getElementById("ANOTACAO"),
          "blur",
          WebSAGE.writeProperties
        );
        Core.addEventListener(
          WebSAGE.g_win_cmd.document.getElementById("CBBLKCMD"),
          "click",
          WebSAGE.writeProperties
        );

        if (!(Q & 0x20)) {
          // ponto digital
          WebSAGE.g_win_cmd.document.getElementById(
            "ESTADO_HID"
          ).style.display = "";
          if (ID.charAt(21) == "M" || SIMULACAO == 1 || SIMULACAO == 2) {
            // permite alterar valor de ponto manual
            WebSAGE.g_win_cmd.document.getElementById(
              "DIVALTVALOR"
            ).style.display = "";

            Core.addEventListener(
              WebSAGE.g_win_cmd.document.getElementById("CBALTVALOR"),
              "click",
              function() {
                WebSAGE.g_win_cmd.document.getElementById(
                  "CBALTVALOR"
                ).style.display = "none";
                WebSAGE.g_win_cmd.document.getElementById(
                  "DIVALTVALORDIG"
                ).style.display = "";
              }
            );

            WebSAGE.g_win_cmd.document.getElementById(
              "rbNovoValor"
            ).nextSibling.data = ST_ON;
            WebSAGE.g_win_cmd.document.getElementById(
              "rbNovoValorOff"
            ).nextSibling.data = ST_OFF;
            Core.addEventListener(
              WebSAGE.g_win_cmd.document.getElementById("rbNovoValor"),
              "click",
              WebSAGE.writeProperties
            );
            Core.addEventListener(
              WebSAGE.g_win_cmd.document.getElementById("rbNovoValorOff"),
              "click",
              WebSAGE.writeProperties              
            );
          }
        }

        // torna visível botão de comandar, caso haja comando associado
        if (CNPTO != 0) {
          WebSAGE.g_win_cmd.document.getElementById("COMANDAR").style.display =
            "";
          Core.addEventListener(
            WebSAGE.g_win_cmd.document.getElementById("COMANDAR"),
            "click",
            WebSAGE.prejanelaComando
          );
        }

        // WebSAGE.mostraDestaqPonto(NPTO);

        // get nonblocking annotation
        WebSAGE.g_win_cmd.document.getElementById("ANOTACAODOC").value = DNOTES[NPTO];

        Core.addEventListener(
          WebSAGE.g_win_cmd.document.getElementById("ANOTACAODOC"),
          "blur",
          WebSAGE.writeProperties
        );
      }

      // bloqueio automático de comando por presença de anotação
      if (
        CNPTO != 0 &&
        WebSAGE.g_win_cmd.document.getElementById("ANOTACAO") !== null
      ) {
        if (WebSAGE.g_win_cmd.document.getElementById("ANOTACAO").value != "") {
          WebSAGE.g_win_cmd.document.getElementById("COMANDAR").disabled = true;
          WebSAGE.g_win_cmd.document.getElementById("DIVBLKCMD").style.display =
            "";
        } else {
          WebSAGE.g_win_cmd.document.getElementById(
            "COMANDAR"
          ).disabled = false;
          WebSAGE.g_win_cmd.document.getElementById("DIVBLKCMD").style.display =
            "none";
          WebSAGE.g_win_cmd.document.getElementById("CBBLKCMD").checked = false;
        }

        if (WebSAGE.g_win_cmd.document.getElementById("CBBLKCMD").checked) {
          WebSAGE.g_win_cmd.document.getElementById(
            "COMANDAR"
          ).disabled = false;
        }

        if (WebSAGE.g_win_cmd.document.getElementById("COMANDAR").disabled) {
          WebSAGE.g_win_cmd.document.getElementById("COMANDAR").title =
            Msg.BlqAnot;
        } else {
          WebSAGE.g_win_cmd.document.getElementById("COMANDAR").title =
            Msg.AcessCmd;
        }

        if (Q & 0x2000) {
          WebSAGE.g_win_cmd.document.getElementById(
            "DIVCMDBLKBUT"
          ).style.display = "none";
          WebSAGE.g_win_cmd.document.getElementById(
            "SPCMDINTERTRAV"
          ).style.display = "";
        } else {
          WebSAGE.g_win_cmd.document.getElementById(
            "DIVCMDBLKBUT"
          ).style.display = "";
          WebSAGE.g_win_cmd.document.getElementById(
            "SPCMDINTERTRAV"
          ).style.display = "none";
        }
      }

      if (!userHasRight('sendCommands'))
        WebSAGE.g_win_cmd.document.getElementById('COMANDAR').disabled = true;
      if (!userHasRight('enterAnnotations'))
        WebSAGE.g_win_cmd.document.getElementById('ANOTACAO').disabled = true;
      if ( (F[NPTO] & 0x0c) == 0x0c && !userHasRight('enterManuals'))
        WebSAGE.g_win_cmd.document.getElementById('CBALTVALOR').disabled = true;
      if (!userHasRight('substituteValues'))
        WebSAGE.g_win_cmd.document.getElementById('CBALTVALOR').disabled = true;
      if (!userHasRight('enterNotes'))
        WebSAGE.g_win_cmd.document.getElementById('ANOTACAODOC').disabled = true;
      if (!userHasRight('disableAlarms'))
        WebSAGE.g_win_cmd.document.getElementById('CBALRIN').disabled = true;
      if (!userHasRight('enterLimits')) {
        WebSAGE.g_win_cmd.document.getElementById('LIMSUP').disabled = true;
        WebSAGE.g_win_cmd.document.getElementById('LIMINF').disabled = true;
        WebSAGE.g_win_cmd.document.getElementById('HISTER').disabled = true;
      }

      WebSAGE.g_timerID = setTimeout("WebSAGE.showValsInfo2(1)", 2000);
    } catch (err) {
      $("#SP_STATUS").text(err.name + ": " + err.message + " [2]");
      document.getElementById("SP_STATUS").title = err.stack;
    }
  },

  prejanelaComando: function() {
    clearTimeout(WebSAGE.g_timerID);
    WebSAGE.janelaComando(CNPTO);
  },

  // busca dados do servidor e prepara chamada temporizada de showValsCmd para
  janelaComando: function(cmdPointKey) {
    NPTO = 0;
    CNPTO = 0;
    var arrpnt = [cmdPointKey];
    WebSAGE.getRealtimeData( arrpnt, true,
          prop => { 
          CNPTO = cmdPointKey; 
          ESTACAO = prop.group1;
          NPTO = prop.supervisedOfCommand;
          CST_ON = prop.stateTextTrue;
          CST_OFF = prop.stateTextFalse;
          CDESC = prop.description;
          CID = TAGS[cmdPointKey];
          setTimeout(WebSAGE.showValsCmd1, 100);
          }
        );  
  },

  // Mostra dados sobre o comando na respectiva janela
  showValsCmd1: function() {

    if (WebSAGE.g_win_cmd.window != undefined && WebSAGE.g_win_cmd) {
      // fecha janela info
      WebSAGE.g_win_cmd.window.close();
    }

    if (CNPTO == 0 || CNPTO === undefined) {
      return;
    }

    WebSAGE.g_win_cmd = NPTO; // mark window to be opened for point NPTO

    // open new command dialog, wait .5s, will show data in the showValsCmd2 function
    setTimeout(
      "WebSAGE.g_win_cmd=window.open('dlgcomando.html','wscomando','dependent=yes,height=450,width=400,toolbar=no,directories=no,status=no,menubar=no,scrollbars=no,resizable=no,modal=yes');",
      500
    );

    // the new window will call showValsCmd2
  },

  // Show command information in the command dialog
  showValsCmd2: function() {

    var se;
    if (WebSAGE.getFlags(NPTO) & 0x2000) {
      // Interlocked command?
      WebSAGE.g_win_cmd.close();
    }

    se = ESTACAO;
    se = se + "-";

    WebSAGE.writeElemByIdWnd(WebSAGE.g_win_cmd, "fc_inst", ESTACAO);
    WebSAGE.writeElemByIdWnd(
      WebSAGE.g_win_cmd,
      "fc_mod",
      CDESC.substring(0, CDESC.indexOf("-"))
    );
    WebSAGE.writeElemByIdWnd(
      WebSAGE.g_win_cmd,
      "fc_info",
      CDESC.substring(CDESC.indexOf("-") + 1)
    );

    WebSAGE.writeElemByIdWnd(WebSAGE.g_win_cmd, "NPONTO_SUP", NPTO + "-" + ID);
    WebSAGE.writeElemByIdWnd(WebSAGE.g_win_cmd, "DESCR_SUP", se + DESC);

    if (!(WebSAGE.getFlags(NPTO) & 0x20)) {
      // digital
      if (V[NPTO] > 0) {
        WebSAGE.writeElemByIdWnd(
          WebSAGE.g_win_cmd,
          "ESTADO_SUP",
          ST_OFF + " (" + Msg.EstadoAtual + ")"
        ); // não zero é off
        // se o estado atual (off) bate com o valor do comando off (3 primeiras letras do texto),
        // assume que deve a intenção é comandar ON, portanto sombreia a opção OFF
        if (
          CST_OFF.toUpperCase().substring(0, 2) ===
            ST_OFF.toUpperCase().substring(0, 2) &&
          CST_ON.toUpperCase().substring(0, 2) !==
            ST_OFF.toUpperCase().substring(0, 2)
        ) {
          WebSAGE.g_win_cmd.document.getElementById("CMD_OFF").style.color = "darkgray";
        }
      } else {
        WebSAGE.writeElemByIdWnd(
          WebSAGE.g_win_cmd,
          "ESTADO_SUP",
          ST_ON + " (" + Msg.EstadoAtual + ")"
        ); // zero é on
        // se o estado atual (on) bate com o valor do comando on (3 primeiras letras do texto),
        // assume que deve a intenção é comandar OFF, portanto sombreia a opção ON
        if (
          CST_ON.toUpperCase().substring(0, 2) ===
            ST_ON.toUpperCase().substring(0, 2) &&
          CST_OFF.toUpperCase().substring(0, 2) !==
            ST_ON.toUpperCase().substring(0, 2)
        ) {
          WebSAGE.g_win_cmd.document.getElementById("CMD_ON").style.color = "darkgray";
        }
      }
      WebSAGE.g_win_cmd.document.getElementById("ESTADO_HID").style.display = "";
    } else {
      // analógico
      WebSAGE.writeElemByIdWnd(
        WebSAGE.g_win_cmd,
        "VALOR_SUP",
        V[NPTO] + " " + UNIDADE + " (" + Msg.QValor + ")"
      );
      WebSAGE.g_win_cmd.document.getElementById("VALOR_HID").style.display = "";
    }

    WebSAGE.writeElemByIdWnd(WebSAGE.g_win_cmd, "CNPONTO_SUP", CNPTO + "-" + CID);
    WebSAGE.writeElemByIdWnd(WebSAGE.g_win_cmd, "CDESCR_SUP", se + CDESC);

    WebSAGE.g_win_cmd.document.getElementById(
      "CMD_OFF"
    ).text = CST_OFF.toUpperCase();
    WebSAGE.g_win_cmd.document.getElementById(
      "CMD_ON"
    ).text = CST_ON.toUpperCase();
    WebSAGE.g_win_cmd.document.getElementById("EXECUTAR").style.display = "";
    WebSAGE.g_win_cmd.document.getElementById("EXECUTAR").disabled = true;
    WebSAGE.g_win_cmd.document.getElementById("COMANDO").style.display = "none";
    WebSAGE.g_win_cmd.document.getElementById("COMANDOANA").style.display =
      "none";

    if (CST_OFF != "" || CST_ON != "") {
      // Digital Command
      WebSAGE.g_win_cmd.document.getElementById("COMANDO").style.display = "";
    } else {
      // Analog Command
      WebSAGE.g_win_cmd.document.getElementById("COMANDOANA").style.display =
        "";
      WebSAGE.g_win_cmd.document.getElementById("COMANDOANA").value = V[NPTO];
    }
  },

  directCommandExec: function(point, value) {
    if  (isNaN(parseFloat(value))){
    if (value === "ON" || value === "on" || value === true || value === "TRUE" || value == "true" ) value = 1;
    else if (value === "OFF" || value === "off" || value === false || value === "FALSE" || value == "false" ) value = 0;
    else {
      console.log("Invalid command value!");
      return;
      } 
    } else {
      value = parseFloat(value)
    }

    // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.WriteRequest // write data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1500,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
        NodesToWrite: [
          {
            NodeId: {
              IdType: OpcKeyType.Numeric, // type: numeric key
              Id: point, // numeric key for the point
              Namespace: OpcNamespaceMongodb
            },
            AttributeId: OpcAttributeId.Value, // OPC attribute to write: Value
            Value: {
              Type: OpcValueTypes.Double,
              Body: value
            }
          }
        ]
      }
    }

    fetchTimeout("/Invoke/", 1500, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if ( (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle || !data.Body.Results) ||
             (data.ServiceId !== OpcServiceCode.WriteResponse || data.Body.ResponseHeader.RequestHandle !== RequestHandle) ||
             (data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good)) {
          CNPTO = 0;
          WebSAGE.g_win_cmd.document.getElementById("ACK_CMD").textContent = "Error!";
          return;
        }
        if ( data.Body.Results[0] !== OpcStatusCodes.Good ){
          CNPTO = 0;
          WebSAGE.g_win_cmd.document.getElementById("ACK_CMD").textContent = "Error!";
        }

        // success
        WebSAGE.g_win_cmd.document.getElementById("ACK_CMD").textContent = " ... ";
        CHANDLE = data.Body._CommandHandles[0];

        // Command log in browser's localStorage
        if (storageAvailable("localStorage")) {
          var lastlogcnt = 0;
          if (localStorage.hasOwnProperty("lastlogcnt"))
            lastlogcnt = parseInt(localStorage["lastlogcnt"]);
          lastlogcnt++;
          lastlogcnt = lastlogcnt % 1000; // circular buffer of 1000
          localStorage[printf("%03d", lastlogcnt)] =
            Date() + " Point:" + point + " Id:?" + " Value:" + value;
          localStorage["lastlogcnt"] = lastlogcnt;
        }
      })
      .catch(err => {
        CNPTO = 0;
        WebSAGE.g_win_cmd.document.getElementById("ACK_CMD").textContent = "Error!";
      });
  },

  executeCommand: function(cmd_val) {
    WebSAGE.directCommandExec(CNPTO, cmd_val);
  },

  // função auxiliar para escrever dados na janela de comando pelo id do objeto
  writeElemByIdWnd: function(win, id, txt) {
    if ("$" in win)
      win.$("#" + id).text(txt);
  },

  getCommandAckStatus: function() {
    // track command acknowledgment from server/protocol, use CHANDLE to verify results

    if ( CNPTO == 0 ) // command cancelled
      return;

    // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.ReadRequest // READ, query command ack results reading attribute 12 (EventNotifier)
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1250,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
        MaxAge: 0,
        NodesToRead: [
          {
            NodeId: {
              IdType: OpcKeyType.Numeric, // numeric key
              Id: parseInt(CNPTO), // the command point numeric key
              Namespace: OpcNamespaceMongodb
            },
            AttributeId: OpcAttributeId.EventNotifier,
            ClientHandle: CHANDLE // pass the command handle (in the standard this is an integer but here is MongoDB OID string)
          }
        ]
      }
    }

    fetchTimeout("/Invoke/", 1250, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle)
          return;

        // response must have same request handle and be a data change notification response or service fault 
        if (data.Body.ResponseHeader.RequestHandle !== RequestHandle ||
            (data.ServiceId !== OpcServiceCode.DataChangeNotification && data.ServiceId !== OpcServiceCode.ServiceFault ) ||
            typeof data.Body.MonitoredItems !== "object" ){
          console.log("Invalid or unexpected service response!");
          return;
          }
        if ( data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good ){
          console.log("Service error!");
          return;
        }

      if ( data.Body.MonitoredItems[0].Value.StatusCode === OpcStatusCodes.BadWaitingForResponse )
        WebSAGE.g_win_cmd.document.getElementById("ACK_CMD").textContent = " ??? ";
      else   
      if ( data.Body.MonitoredItems[0].Value.StatusCode === OpcStatusCodes.Good )
        WebSAGE.g_win_cmd.document.getElementById("ACK_CMD").textContent = "Ok!";
      else   
      if ( data.Body.MonitoredItems[0].Value.StatusCode === OpcStatusCodes.Bad )
        WebSAGE.g_win_cmd.document.getElementById("ACK_CMD").textContent = "Rejected!";   
      })
      .catch(function (error) {
        console.log(error);
      });
  },

  writeProperties: function() {
    if (!WebSAGE.g_win_cmd) {
      return;
    }
    if (typeof WebSAGE.g_win_cmd.window != "object") {
      return;
    }
    if (typeof WebSAGE.g_win_cmd.window.closed === "undefined") {
      return;
    }
    if (WebSAGE.g_win_cmd.window.closed) {
      return;
    }
    if (WebSAGE.g_win_cmd.document === undefined) {
      return;
    }

    if (WebSAGE.g_win_cmd.document.getElementById("CBBLKCMD").checked) {
      // desbloqueio do comando apaga anotação
      WebSAGE.g_win_cmd.document.getElementById("ANOTACAO").value = "";
    }

    var li = parseFloat(
      WebSAGE.g_win_cmd.document.getElementById("LIMINF").value
    );
    if (isNaN(li)) {
      li = -Number.MAX_VALUE;
    }

    var ls = parseFloat(
      WebSAGE.g_win_cmd.document.getElementById("LIMSUP").value
    );
    if (isNaN(ls)) {
      ls = Number.MAX_VALUE;
    }

    var hs = parseFloat(
      WebSAGE.g_win_cmd.document.getElementById("HISTER").value
    );
    if (isNaN(hs)) {
      hs = 0;
    }

    LIMINFS[NPTO] = li;
    LIMSUPS[NPTO] = ls;

    //WebSAGE.g_win_cmd.document.getElementById("LIMINF").value = li;
    //WebSAGE.g_win_cmd.document.getElementById("LIMSUP").value = ls;
    //WebSAGE.g_win_cmd.document.getElementById("HISTER").value = hs;

    ANOTS[NPTO] = WebSAGE.g_win_cmd.document.getElementById("ANOTACAO").value;
    DNOTES[NPTO] = WebSAGE.g_win_cmd.document.getElementById("ANOTACAODOC").value;

    var newValue = undefined;
    var substituted = undefined;
    if (WebSAGE.getFlags(NPTO) & 0x20) {
      if (WebSAGE.g_win_cmd.document.getElementById("NOVOVALOR").value !== "")
      if (!isNaN( parseFloat(WebSAGE.g_win_cmd.document.getElementById("NOVOVALOR").value) )){
        newValue = parseFloat(WebSAGE.g_win_cmd.document.getElementById("NOVOVALOR").value);
        substituted = true;
      }
    } else {
      if (WebSAGE.g_win_cmd.document.getElementById("rbNovoValor").checked === true){
        newValue = 1;
        substituted = true;
      }
      else
      if (WebSAGE.g_win_cmd.document.getElementById("rbNovoValorOff").checked === true){
        newValue = 0;
        substituted = true;
      }        
    }

    // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.WriteRequest // write data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1250,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
        NodesToWrite: [
          {
            NodeId: {
              IdType: OpcKeyType.Numeric, // type: numeric key
              Id: parseInt(NPTO), // numeric key for the point
              Namespace: OpcNamespaceMongodb
            },
            AttributeId: OpcAttributeId.Description, // OPC attribute to write: Description
            Value: {
              _Properties: {
                alarmDisabled: WebSAGE.g_win_cmd.document.getElementById('CBALRIN')
                  .checked
                  ? true
                  : false,
                annotation: ANOTS[NPTO],
                loLimit: li,
                hiLimit: ls,
                hysteresis: hs,
                notes: DNOTES[NPTO],
                ...(typeof newValue !== "undefined" ? {newValue: newValue} : {}),
                ...(typeof substituted !== "undefined" ? {substituted: substituted} : {})
              }
            }
          }
        ]
      }
    }

    fetchTimeout("/Invoke/", 1250, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle || !data.Body.Results){
          console.log("Error writing point data! Invalid or unexpected response.");
          return;
        }
        if (data.ServiceId !== OpcServiceCode.WriteResponse || data.Body.ResponseHeader.ServiceResult != 0){
          console.log("Error writing point data! Service error.");
          return;
        }
        if (data.Body.ResponseHeader.RequestHandle !== RequestHandle){
          console.log("Error writing point data! Invalid response handle.");
          return;
        }

        // success
        // console.log("write ok");
      });
  },

mudaFiltro : function()
{
if ( WebSAGE.g_filtroSE != document.getElementById('FiltroSE').value || 
     WebSAGE.g_filtroMod != document.getElementById('FiltroMod').value )  
  {
  WebSAGE.writeElemByIdWnd( window, 'ATUALIZACAO', ' ' );
  WebSAGE.writeElemByIdWnd( window, 'NUMREGS', ' ' );
  clearTimeout( WebSAGE.g_toutID );
  WebSAGE.g_tbl.clearAll();
  document.getElementById("cbMostraCmd").checked = false;
  document.getElementById("cbForaNormal").checked = false;

  WebSAGE.g_filtroSE = document.getElementById('FiltroSE').value;
  WebSAGE.g_filtroMod = document.getElementById('FiltroMod').value;
  L = [];
  if ( WebSAGE.g_filtroSE == '')
    {
        clearTimeout( WebSAGE.g_timeoutFalhaID );
    }
  else
    {
    clearTimeout( WebSAGE.g_toutID );
    WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, 100 );
    WebSAGE.g_muda_mod = 1;
    }
  }
},

// get group1 (stations) list
getGroup1List : function() 
{
    // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.Extended_RequestUniqueAttributeValues // read data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1500,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
      AttributeId: OpcAttributeId.ExtendedGroup1
      }
    }

    fetchTimeout("/Invoke/", 1500, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle || !data.Body.Results){
          console.log("RequestUniqueAttributeValues invalid service response!");
          return;
          }

        // response must have same request handle and be a read response or service fault 
        if (data.Body.ResponseHeader.RequestHandle !== RequestHandle ||
            (data.ServiceId !== OpcServiceCode.Extended_ResponseUniqueAttributeValues && data.ServiceId !== OpcServiceCode.ServiceFault)
           ){
          console.log("RequestUniqueAttributeValues invalid or unexpected service response!");
          return;
          }
        if ( data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good ){
          console.log("RequestUniqueAttributeValues service error!");
          return;
        }

        GROUP1_LIST=[];
        GROUP1_LIST.push("");
        if ("Results" in data.Body)
        data.Body.Results.map(element => {
          if (element.Value.Body !== null && element.Value.Body !== "")
            GROUP1_LIST.push(element.Value.Body);
          return element;
        });
        WebSAGE.mostraSEs();
      })
      .catch(function (error) {
        console.log(error);
      });

},

// mostra lista de estações
mostraSEs : function() 
{
var Lista = GROUP1_LIST;
var i = 0;

Lista.sort();
document.getElementById("SELSE").options.length = 0;

for (var se in Lista)
 {
 if ( Lista[i] != undefined )
   document.getElementById("SELSE").options[i] = new Option(Lista[i], Lista[i]);
 i++;
 }

// ve se recebeu algum modulo (SE+MODULO) para mostrar pela URL, caso positivo já usa para filtrar  
var se = gup("SUBST");
var mod = decodeURIComponent(gup("BAY"));

if ( se != "" )
  {
   document.getElementById('FiltroSE').value = se;
   document.getElementById('FiltroMod').value = mod;
   setTimeout( WebSAGE.mudaFiltro, 10 );
  }

// Filters saved in localStorage
var almFilter={};
if (storageAvailable('localStorage')) 
{
var storedData = localStorage.getItem("almFilter");
  if (storedData) {
  almFilter = JSON.parse(storedData);  
  } 
}   

if ( WebSAGE.isAlarmsViewer() )
  {
  // container to put in per substation filters
  $( "body" ).append("<div id='DIV_SUBSTALMFILT' style='background-color:#bbb;overflow:auto;top:42px;left:0;width:100%;height:35px;position:absolute;text-align:center;'>"+
                    "<div id='ZZABC1' style='clear:left;padding-top:5px;padding-bottom:2px;'>" +
                    "<input id='ZZCB_SELECTALL' type='button' value='Select All'>&nbsp;&nbsp;" +
                    "<input id='ZZCB_UNSELECTALL' type='button' value='Unselect All'>" +
                    "</div>" +
                    "</div>");
  document.getElementById("ZZCB_SELECTALL").value = Msg.SelectAll;
  document.getElementById("ZZCB_UNSELECTALL").value = Msg.UnselectAll;
  document.getElementById("ZZCB_SELECTALL").
          addEventListener("click", 
          function(event) { 
            for ( var sorp in WebSAGE.g_alminfo )
              {
              if (WebSAGE.g_alminfo[sorp].is_subst)
                {
                  WebSAGE.g_alminfo[sorp].filterout=true;
                }
              }
              WebSAGE.applyTableStyle();
          });
  document.getElementById("ZZCB_UNSELECTALL").
          addEventListener("click", 
          function(event) { 
            for ( var sorp in WebSAGE.g_alminfo )
              {
              if (WebSAGE.g_alminfo[sorp].is_subst)
                {
                  WebSAGE.g_alminfo[sorp].filterout=false;
                }
              }
              WebSAGE.applyTableStyle();
          });
  document.getElementById("DIV_SUBSTALMFILT").
          addEventListener("mouseover", 
          function(event) { 
            document.getElementById("DIV_SUBSTALMFILT").style.height=""; 
          });
  document.getElementById("DIV_SUBSTALMFILT").
          addEventListener("mouseout", 
          function(event) { 
            document.getElementById("DIV_SUBSTALMFILT").style.height="35px"; 
          });
  
  TabularViewer_CustomFiltersSelectors.forEach(element => {
    document.getElementById('ZZABC1').insertAdjacentHTML('beforeend', '&nbsp;&nbsp;<input id="ZZCB_'+ element.name +'" type="button" value="'+element.name+'">');
    document.getElementById('ZZCB_'+ element.name)._group1List = element.group1List;
    document.getElementById('ZZCB_'+ element.name).addEventListener('click', function() {
      for ( var sorp in WebSAGE.g_alminfo )
            {
            if (WebSAGE.g_alminfo[sorp].is_subst)
              {
                if (document.getElementById('ZZCB_'+ element.name)._group1List.includes(sorp))
                  WebSAGE.g_alminfo[sorp].filterout=false;
                else
                  WebSAGE.g_alminfo[sorp].filterout=true;
              }
            }
            WebSAGE.applyTableStyle();
    })
  });

  // create substation filters inside a div
  Lista.reverse();
  for (var se in Lista)
    { 
    if (Lista[se] == "") 
      continue; 
    var filterId = toValidId(Lista[se])   
    var ytop = 2;
    if ( document.getElementById( "DIV_" + Lista[se] ) === null )
    $( "#DIV_SUBSTALMFILT" ).prepend(
    "<div id='DIV_" + filterId + "' " +
        " onclick='WebSAGE.g_alminfo[&quot;" + Lista[se] + "&quot;].filterout = ! WebSAGE.g_alminfo[&quot;" + Lista[se] + "&quot;].filterout;WebSAGE.applyTableStyle();'" +
        " style='position:relative;float:left;text-align:center;margin-bottom:7px;margin-left:3px;top:" + ytop + "px;" + 
        "cursor:pointer;box-shadow: 1px 1px 1px #666666;background-color:white;min-width:48px;height:20px;border-radius:4px;border:1px solid #777;padding-top:2px;padding-left:2px;padding-right:2px;line-height:80%;font-size:15px;font-family:trebuchet ms,tahoma,helvetica,arial'>"+ 
      Lista[se] + 
      "<br>" + 
      "<span id='SP_" + filterId +  "_ACK" +
          "' style='float:right;-webkit-filter:contrast(.7);text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;background-color:silver;border-color:silver;'>" + 
      "0</span>" + 
      "<span id='SP_" + filterId + "_NACK" +
          "' style='float:right;text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;background-color:silver;border-color:silver;'>" + 
      "0</span>" + 
    "</div>" );

    if ( typeof( WebSAGE.g_alminfo[ Lista[se] ] ) === 'undefined' )
      {
      WebSAGE.g_alminfo[ Lista[se] ] =  { countnack: 0, 
                                          countack: 0, 
                                          filterId: filterId,
                                          filterout: false, 
                                          minpriorack: 10, 
                                          minpriornack: 10, 
                                          is_subst: true };
      }      

    if( WebSAGE.isAlarmsViewer() )
    if ( Lista[se] in almFilter ) 
      {
        WebSAGE.g_alminfo[ Lista[se] ].filterout = almFilter[Lista[se]];
      }
    }

  var xleft = 230 + 68;
    var ytop = 5;
    for ( var priority in [0,1,2,3,4,5,7,8,9] ) 
    {
      if ( document.getElementById( "DIV_" + priority ) === null && priority <= 3 )
        {
          $( "body" ).append(
            "<div id='DIV_" + priority + "' " +
               " onclick='WebSAGE.g_alminfo[&quot;" + priority + "&quot;].filterout = ! WebSAGE.g_alminfo[&quot;" + priority + "&quot;].filterout;WebSAGE.applyTableStyle();'" +
               " style='position:absolute;float:left;text-align:center;top:" + ytop + "px;" + 
                        "left:" + ( xleft + priority * 68 ) + "px;" + 
                       "cursor:pointer;box-shadow: 1px 1px 1px #666666;background-color:white;min-width:48px;height:20px;border-radius:4px;border:1px solid #777;padding-top:2px;padding-left:2px;padding-right:2px;line-height:80%;font-size:15px;font-family:trebuchet ms,tahoma,helvetica,arial'>"+ 
              priority + 
              "<br>" + 
              "<span id='SP_" + priority +  "_ACK" +
                  "' style='float:right;-webkit-filter:contrast(.7);text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;background-color:silver;border-color:silver;'>" + 
              "0</span>" + 
              "<span id='SP_" + priority + "_NACK" +
                  "' style='float:right;text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;background-color:silver;border-color:silver;'>" + 
              "0</span>" + 
            "</div>" );
        }

      if ( priority in almFilter ) 
        {
          WebSAGE.g_alminfo[ priority ] = {};
          WebSAGE.g_alminfo[ priority ].filterout = almFilter[priority];
          WebSAGE.g_alminfo[ priority ].filterId = priority;
        }
    }
    WebSAGE.applyTableStyle();

    // sort divs in name (id) order
    var main = document.getElementById( 'DIV_SUBSTALMFILT' );
    if (main !== null)
    [].map.call( main.children, Object ).sort( function ( a, b ) {
        return ((a.id > b.id)? 1 : -1);
    }).forEach( function ( elem ) {
        main.appendChild( elem );
    });
  }

  if ( WebSAGE.isAlarmsViewer() )
      {
      WebSAGE.mostraTodosAnormais();
      }

// document.getElementById("SELSE").onChange=WebSAGE.mudaSE; 
},

// Processa mudança da SE selecionada
mudaSE : function() 
{ 
var v = document.getElementById("SELSE").options[document.getElementById("SELSE").selectedIndex].value;
document.getElementById('FiltroSE').value = v ;  
document.getElementById('FiltroMod').value = "";
document.getElementById("SELMOD").options.length = 0;
  
WebSAGE.mudaFiltro();
document.getElementById("SELSE").onchange = function() {};
document.getElementById("SELSE").options[0].selected = 1;  
document.getElementById('SELSE').blur();
},

// mostra lista de opções de móulos
mostraMods : function() 
{
var lmods = [];
var descr = [];
var i;
var cnt;

if ( WebSAGE.isAlarmsViewer() )
  return;
if ( document.getElementById('FiltroSE').value == "" )
  {
  document.getElementById("SELMOD").options.length = 0;
  return;
  }

WebSAGE.g_muda_mod = 0;

// assemble bay list
cnt = 0;
for ( i in L )
  {
  descr = WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_DESCR).trim().split(WebSAGE.g_split_mod_descr);
  if ( lmods.indexOf( descr[0] ) === -1 ) 
    {
    lmods[cnt] = descr[0];
    cnt++;
    }
  }

if ( lmods.length < 2 ) // if 1 bay only, presents no choice
  return;

lmods[lmods.length] = ""; // 1st option is empty
lmods.sort();
document.getElementById("SELMOD").options.length = 0;

// options list
cnt = 0;
for ( cnt = 0; cnt < lmods.length ; cnt++ )
  {
  document.getElementById("SELMOD").options[cnt] = new Option( lmods[cnt], lmods[cnt] );
  }
},

// Filtra pelo módulo  
mudaMod : function()
{
  document.getElementById('FiltroMod').value =
    document.getElementById("SELMOD").options[document.getElementById("SELMOD").selectedIndex].value;
  WebSAGE.mudaFiltro();
  document.getElementById("SELMOD").options.length = 0;
},

callServer : function() 
{
     clearTimeout( WebSAGE.g_toutID );

     if ( WebSAGE.g_waitingServer ) // se ainda está esperando pelo servidor
       { // dá mais um tempo para rechamar-se e cai fora
         WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh / 2 );
         return;
       }       

    // testa se não está bloqueado
    if ( document.getElementById(WebSAGE.g_cbBloqAtu).checked == false )
      {
        // antes de chamar o servidor
        // prepara uma função que falha todos os pontos se der timeout na chamada do servidor
        clearTimeout( WebSAGE.g_timeoutFalhaID );
        WebSAGE.g_timeoutFalhaID = setTimeout( WebSAGE.falhaTudo, WebSAGE.g_timeOutFalha );
        // prepara função de callback que será chamada pelo script a ser devolvido pelo servidor
        cbf_F = function() 
                  { 
                  clearTimeout( WebSAGE.g_timeoutFalhaID ); 
                  WebSAGE.g_waitingServer = 0; 
                  setTimeout( WebSAGE.getServerStatus, WebSAGE.g_timeOutRefresh/2 );
                  setTimeout( WebSAGE.showVals, 100 ); 
                  };
        // sinaliza que vai esperar resposta do servidor 
        WebSAGE.g_waitingServer = 1;

        Ventoinha.pulse();

        // chama o servidor, executa o script devolvido
        if (WebSAGE.isAlarmsViewer())
          WebSAGE.getRealtimeFilteredData("", "", true, cbf_F);
        else  
          WebSAGE.getRealtimeFilteredData(WebSAGE.g_filtroSE, WebSAGE.g_filtroMod, false, cbf_F);
        WebSAGE.g_pass++;        
        WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh );
      }
},

// pega status do servidor, variável ALARMBEEP
getServerStatus: function() {

WebSAGE.getRealtimeData( [BEEP_POINTKEY, CNTUPDATES_POINTKEY], true,
  prop => { 
  // se mudou estado de alarme, poderia lista logo
  if ( NUM_VAR_ANT != WebSAGE.getValue(CNTUPDATES_POINTKEY) ) {
  }

  ALARMBEEP = WebSAGE.getValue(BEEP_POINTKEY);
  NUM_VAR_ANT = WebSAGE.getValue(CNTUPDATES_POINTKEY);
  if ( ALARMBEEP )
      {
      document.getElementById( "SILENCIA_ID" ).style.display = "";
      }
    else
      {
      document.getElementById( "SILENCIA_ID" ).style.display = "none";
      }
  });   
},

getRealtimeFilteredData: function (group1Filter, group2Filter, onlyAlarms, callbacksuccess) {

  var ContentFilter = []
    var ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }

    if ( typeof group1Filter === "string" && group1Filter.trim()!=="" ){
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "group1"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: group1Filter});
      ContentFilter.push(ContFiltElem);
    }
    if ( typeof group2Filter === "string" && group2Filter.trim()!=="" ){
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "group2"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: group2Filter});
      ContentFilter.push(ContFiltElem);
    }
    if (onlyAlarms){
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "alarmed"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: true});
      ContentFilter.push(ContFiltElem);
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "invalid"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: false});
      ContentFilter.push(ContFiltElem);
      ContFiltElem = {
            FilterOperator: OpcFilterOperator.Equals,
            FilterOperands: []
            }
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Attribute, Value: "persistentAlarms"});
      ContFiltElem.FilterOperands.push({FilterOperand: OpcOperand.Literal, Value: true});
      ContentFilter.push(ContFiltElem);
    }

   // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.ReadRequest // read data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1500,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
        Namespace: OpcNamespaceMongodb, // directs query to mongodb instead of postgresql
        ContentFilter: ContentFilter,
        MaxAge: 0,
        TimestampsToReturn: TimestampsToReturn.Both
      }
    }

    fetchTimeout("/Invoke/", 1500, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle){
          console.log("ReadRequest invalid service response!");
          return;
          }

        // response must have same request handle and be a read response or service fault 
        if (data.Body.ResponseHeader.RequestHandle !== RequestHandle ||
            (data.ServiceId !== OpcServiceCode.ReadResponse && data.ServiceId !== OpcServiceCode.ServiceFault)
           ){
          console.log("ReadRequest invalid or unexpected service response!");
          return;
          }
        if ( data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good && 
             data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.GoodNoData) {
          console.log("ReadRequest service error!");
          // check access control denied, in this case go to initial page
          if ( data.Body.ResponseHeader.ServiceResult === OpcStatusCodes.BadUserAccessDenied ||
              data.Body.ResponseHeader.ServiceResult === OpcStatusCodes.BadIdentityTokenInvalid ||
              data.Body.ResponseHeader.ServiceResult === OpcStatusCodes.BadIdentityTokenRejected 
          ) {
             window.onbeforeunload = null;
             window.location.href = "/";
          }
          return;
        }

        WebSAGE.Pass++;

        L = [];
        var prop;

        if ("Results" in data.Body)
        data.Body.Results.map(element => {
          if (typeof element.StatusCode == "number" && element.StatusCode != 0) // reject a bad result
            return;
          if (element.NodeId.IdType != OpcKeyType.String)
            return;

          prop = element._Properties;          
          var pointKey = prop._id;
		  
          var dateAlarm = "";
          if (prop.alarmed && prop.timeTagAlarm!==null)
          if ((new Date(prop.timeTagAlarm)).getFullYear()>1980){
              try{
                dateAlarm = (new Date(prop.timeTagAlarm)).toLocaleString(TabularViewer_LocaleDateTime, TabularViewer_LocaleDateTimeOptions);
              }
              catch(e){
                dateAlarm = (new Date(prop.timeTagAlarm)).toLocaleString();
              }
          }
          var dateField = "";
          if ("SourceTimestamp" in element && element.SourceTimestamp!==null){
            var dt = new Date(element.SourceTimestamp);
            try{
                dateField = dt.toLocaleString(TabularViewer_LocaleDateTime, TabularViewer_LocaleDateTimeOptions);
              }
            catch(e){
                dateField = dt.toLocaleString();
              }
              if (!('fractionalSecondDigits' in TabularViewer_LocaleDateTimeOptions))
                dateField += "." + ("00" + dt.getUTCMilliseconds()).slice(-3);
          }

          var flags = (element.Value.Quality!==OpcStatusCodes.Good?0x80:0x00);
          if ( element.Value.Type === OpcValueTypes.Double ) {
            flags |= 0x20;
            if ('frozen' in prop && prop.frozen === true )
              flags |= 0x1000;
            if ('alarmRange' in prop)
              flags |= (prop.alarmRange !== 0 ? 0x800 : 0x0000);
          }
          if ('alarmed' in prop && prop.alarmed === true )
            flags |= 0x100;
          if ('alarmDisabled' in prop && prop.alarmDisabled === true )
            flags |= 0x400;
          var persistent = "";
          if (prop.alarmState === 1 && element.Value.Body === true  ||
              prop.alarmState === 0 && element.Value.Body === false )
            persistent = "P";

          var valDisplay = element.Value.Body;
          if ( element.Value.Type === OpcValueTypes.Double )
            valDisplay = parseFloat(element.Value.Body).toFixed(3) + " " + prop.unit;
          else
          if ( element.Value.Type === OpcValueTypes.Boolean ){
            if (element.Value.Body === true)
               valDisplay = prop.stateTextTrue;
            else   
               valDisplay = prop.stateTextFalse;
          }             

          if (prop.origin!=="command" && (prop._id > 0 || group1Filter === "_System" ) )
          L.push(
            "" + pointKey + WebSAGE.g_split_line_code +
            element.NodeId.Id + WebSAGE.g_split_line_code +
            prop.group1 + WebSAGE.g_split_line_code +
            prop.group2 + WebSAGE.g_split_mod_descr + prop.ungroupedDescription  + WebSAGE.g_split_line_code +
            valDisplay  + WebSAGE.g_split_line_code +
            flags + WebSAGE.g_split_line_code + // flags
            prop.commandOfSupervised + WebSAGE.g_split_line_code + 
            "" + prop.priority + (prop.alarmed?"L":"") + persistent +
              (prop.annotation!=""?"A":"") + 
              (prop.alarmDisabled?"I":"") + 
              (prop.isEvent?"E":"") + 
              (('frozen' in prop && prop.frozen===true)?"U":"") + 
              (prop.commandOfSupervised!==0?"K":"") + 
              (prop.origin==="manual"?"M":"") + 
              (("timeTagAtSourceOk" in prop && prop.timeTagAtSourceOk === "false")?"T":"") +
              (element.Value.Quality!==OpcStatusCodes.Good?"F":"") +
              WebSAGE.g_split_line_code +
            0  + WebSAGE.g_split_line_code + // normal state
            dateAlarm + WebSAGE.g_split_line_code +
            dateField
          )

          if (element.Value.Type === OpcValueTypes.Boolean) {
            V[pointKey] = element.Value.Body?0:1;
            F[pointKey] = (element.Value.Body ? 0x02 : 0x01);
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = valDisplay;
          }
          else if (element.Value.Type === OpcValueTypes.Double) {
            V[pointKey] = parseFloat(element.Value.Body);
            F[pointKey] = 0x20;
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = valDisplay;
          }
          else if (element.Value.Type === OpcValueTypes.String) { 
            V[pointKey] = parseFloat(element.Value.Body);
            F[pointKey] = 0x00;
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = element.Value.Body;
          }
          F[pointKey] |= (element.Value.Quality & 0x80000000 ? 0x80 : 0x00) | 
                        (prop.alarmed ? 0x100 : 0x000) |
                        (prop.alarmDisabled ? 0x400 : 0x000) |
                        (("annotation" in prop && prop.annotation!=="")? 0x200 : 0x000);
          if (prop.origin === "manual")
            F[pointKey] |= 0x0C;
          TAGS[pointKey] = element.NodeId.Id;
          NPTS[element.NodeId.Id] = pointKey;
          if (typeof prop.notes == "string") 
            DNOTES[pointKey] = prop.notes;
          if (typeof prop.annotation == "string") 
            ANOTS[pointKey] = prop.annotation;
          if (typeof prop.description == "string") {
            SUBS[pointKey] = prop.group1;
            BAYS[pointKey] = prop.group2;
            DCRS[pointKey] = (prop.ungroupedDescription!="")? prop.ungroupedDescription : prop.description;
            if (isNaN(prop.hiLimit) || prop.hiLimit === null)
              LIMSUPS[pointKey] = Infinity;
            else  
              LIMSUPS[pointKey] = prop.hiLimit;
            if (isNaN(prop.loLimit) || prop.loLimit === null)
              LIMINFS[pointKey] = -Infinity;
            else
              LIMINFS[pointKey] = prop.loLimit;
            STONS[pointKey] = prop.stateTextTrue;
            STOFS[pointKey] = prop.stateTextFalse;
          }
          return element;
        });
        if ( typeof callbacksuccess == "function" )
           callbacksuccess(prop);
      })
      .catch(function (error) {
        console.log(error);
      });
  },

// obtains realtime data from the server translating it to 
// internal data structures the call the SVG screen update routine
getRealtimeData: function (querykeys, askinfo, callbacksuccess) {
  if (typeof querykeys.length !== "number" || querykeys.length == 0)
      return;
  
  // use OPC web hmi protocol https://prototyping.opcfoundation.org/
  var ServiceId = OpcServiceCode.ReadRequest // read data service
  var RequestHandle = Math.floor(Math.random() * 100000000)
  var req = {
    ServiceId: ServiceId,
    Body: {
      RequestHeader: {
        Timestamp: new Date().toISOString(),
        RequestHandle: RequestHandle,
        TimeoutHint: 1500,
        ReturnDiagnostics: 2,
        AuthenticationToken: null
      },
      MaxAge: 0,
      TimestampsToReturn: TimestampsToReturn.Both
    }
  }

  var NodesToRead = [];
  querykeys.map(element => {
    var IdType, Id;
    if (isNaN(parseInt(element))) { // a tag
      IdType = OpcKeyType.String;
      Id = element;
    }
    else { // a numeric key
      IdType = OpcKeyType.Numeric;
      Id = parseInt(element);
    }
    NodesToRead.push({
      "NodeId": {
        "IdType": IdType,
        "Id": Id,
        "Namespace": OpcNamespaceMongodb
      },
      "AttributeId": (askinfo === true ? OpcAttributeId.Description : OpcAttributeId.Value)
    });
    return element;
  });
  req.Body.NodesToRead = NodesToRead;

  fetchTimeout("/Invoke/", 1500, {
    method: "POST",
    body: JSON.stringify(req),
    headers: {
      "Content-Type": "application/json"
    }
  })
    .then(function (response) {
      return response;
    })
    .then(response => response.json())
    .then(data => {
      if (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle){
        console.log("ReadRequest invalid service response!");
        return;
        }

      // response must have same request handle and be a read response or service fault 
      if (data.Body.ResponseHeader.RequestHandle !== RequestHandle ||
          (data.ServiceId !== OpcServiceCode.ReadResponse && data.ServiceId !== OpcServiceCode.ServiceFault)
          ){
        console.log("ReadRequest invalid or unexpected service response!");
        return;
        }
      if ( data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good && 
           data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.GoodNoData) {
        console.log("ReadRequest service error!");
        // check access control denied, in this case go to initial page
        if ( data.Body.ResponseHeader.ServiceResult === OpcStatusCodes.BadUserAccessDenied ||
             data.Body.ResponseHeader.ServiceResult === OpcStatusCodes.BadIdentityTokenInvalid ||
             data.Body.ResponseHeader.ServiceResult === OpcStatusCodes.BadIdentityTokenRejected 
         ) {
             window.onbeforeunload = null;
             window.location.href = "/";
         }
        return;
      }

      WebSAGE.Pass++;

      var prop;
      if ("Results" in data.Body)
      data.Body.Results.map(element => {
        if (typeof element.StatusCode == "number" && element.StatusCode != 0) // reject a bad result
          return;
        if (element.NodeId.IdType != 1)
          return;
        prop = element._Properties;          
        var pointKey = prop._id;

		if ( pointKey === BEEP_POINTKEY && "beepType" in prop )
			ALARMBEEP_TYPE = prop.beepType;

        if (element.Value.Type === OpcValueTypes.Boolean) {
            V[pointKey] = element.Value.Body?0:1;
            F[pointKey] = (element.Value.Body ? 0x02 : 0x01);
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = prop.valueString;
          }
          else if (element.Value.Type === OpcValueTypes.Double) {
            V[pointKey] = element.Value.Body;
            F[pointKey] = 0x20;
            if (prop.frozen)
              F[pointKey] |= (prop.frozen === true ? 0x1000 : 0x0000);
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = prop.valueString;
          }
          else if (element.Value.Type === OpcValueTypes.String) { 
            V[pointKey] = parseFloat(element.Value.Body);
            F[pointKey] = 0x00;
            T[pointKey] = element.SourceTimestamp;
            S[pointKey] = element.Value.Body;
          }
        F[pointKey] |= (element.Value.Quality & 0x80000000 ? 0x80 : 0x00) | 
                        (prop.alarmed ? 0x100 : 0x000) |
                        (("annotation" in prop && prop.annotation!=="")? 0x200 : 0x000);

        TAGS[pointKey] = element.NodeId.Id;
        NPTS[element.NodeId.Id] = pointKey;
        if (typeof prop.description == "string") {
          SUBS[pointKey] = prop.group1;
          BAYS[pointKey] = prop.group2;
          DCRS[pointKey] = (prop.ungroupedDescription!="")? prop.ungroupedDescription : prop.description;
          if (isNaN(prop.hiLimit) || prop.hiLimit === null)
            LIMSUPS[pointKey] = Infinity;
          else  
            LIMSUPS[pointKey] = prop.hiLimit;
          if (isNaN(prop.loLimit) || prop.loLimit === null)
            LIMINFS[pointKey] = -Infinity;
          else
            LIMINFS[pointKey] = prop.loLimit;
          STONS[pointKey] = prop.stateTextTrue;
          STOFS[pointKey] = prop.stateTextFalse;
          ANOTS[pointKey] = prop.annotation;
          DNOTES[pointKey] = prop.notes;
        }
        return element;
      });
      if ( typeof callbacksuccess == "function" )
          callbacksuccess(prop);
    })
    .catch(function (error) {
      console.log(error);
    });
  },

// acknowledges or quits event alarm
ackAlarm: function( almquit, aggregate, pointId)
{
if (almquit === undefined)
  almquit = 0;
aggregate = 0;
if (pointId === undefined)
  pointId = 0;

  var action = 0;
  if (almquit === 0 && pointId === 0){ // ack all alarms
     action = OpcAcknowledge.AckAllAlarms | OpcAcknowledge.SilenceBeep;
  }
  if (almquit === -1){ // only silcence beep
     action = OpcAcknowledge.SilenceBeep;
  }
  if (almquit === 0 && aggregate === 0 && pointId !== 0){ // ack one alarm
     action = OpcAcknowledge.AckOneAlarm | OpcAcknowledge.SilenceBeep;
  }
    var IdType = OpcKeyType.Numeric;
    if ( isNaN(parseInt(pointId)) )
      IdType = OpcKeyType.String;
    else  
      pointId = parseInt(pointId);

    // use OPC web hmi protocol https://prototyping.opcfoundation.org/
    var ServiceId = OpcServiceCode.WriteRequest // write data service
    var RequestHandle = Math.floor(Math.random() * 100000000)
    var req = {
      ServiceId: ServiceId,
      Body: {
        RequestHeader: {
          Timestamp: new Date().toISOString(),
          RequestHandle: RequestHandle,
          TimeoutHint: 1000,
          ReturnDiagnostics: 2,
          AuthenticationToken: null
        },
        NodesToWrite: [
          {
            NodeId: {
              IdType: IdType, // type: numeric or string key
              Id: pointId, // key for the point
              Namespace: OpcNamespaceMongodb
            },
            AttributeId: OpcAttributeId.ExtendedAlarmEventsAck, // OPC attribute to write: Alarm Ack
            Value: {
              Type: OpcValueTypes.Integer,
              Body: action
            }
          }
        ]
      }
    }

    fetchTimeout("/Invoke/", 1500, {
      method: "POST",
      body: JSON.stringify(req),
      headers: {
        "Content-Type": "application/json"
      }
    })
      .then(function (response) {
        return response;
      })
      .then(response => response.json())
      .then(data => {
        if ( (!data.ServiceId || !data.Body || !data.Body.ResponseHeader || !data.Body.ResponseHeader.RequestHandle || !data.Body.Results) ||
             (data.ServiceId !== OpcServiceCode.WriteResponse || data.Body.ResponseHeader.RequestHandle !== RequestHandle) ||
             (data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.Good && data.Body.ResponseHeader.ServiceResult !== OpcStatusCodes.GoodNoData)) {
              console.log("Ack Alarm Error!");
              return;
        }

        // success
      })
      .catch(err => {
        console.log(err);
      });

},

// open trend visor of point info
tendencias: function() {
  WebSAGE.vis_trend(NPTO);
},

// open trend visor
vis_trend: function(npt) {
  window.open(
    "trend.html?NPONTO=" + npt,
    "Tendencias " + npt,
    "dependent=no,height=400,width=700,location=no,toolbar=no,directories=no,status=no,menubar=no,resizable=yes,modal=no"
  );
  setTimeout("WebSAGE.g_win_cmd.close()", 500);
},

// open plot visor (historical) of point info
curvas: function() {
  if (F[NPTO] & 0x20)
    window.open(
      "/grafana/d/78X6BmvMk/json-scada-history-analog?var-point_tag=" + TAGS[NPTO],
      "History " + TAGS[NPTO],
      "dependent=no,height=600,width=1000,location=no,toolbar=no,directories=no,status=no,menubar=no,resizable=yes,modal=no"
    );
  else
    window.open(
      "/grafana/d/LsXOaz47z/json-scada-history-digital?var-point_tag=" + TAGS[NPTO],
      "History " + TAGS[NPTO],
      "dependent=no,height=600,width=1000,location=no,toolbar=no,directories=no,status=no,menubar=no,resizable=yes,modal=no"
    );
  setTimeout("WebSAGE.g_win_cmd.close()", 500);
},

// open tabular visor of bay, of point info
tabular : function()
{
window.open( 'tabular.html?SUBST=' + ESTACAO + '&BAY=' + MODULO, 'Tabular', 'dependent=no,height=700,width=900,location=no,toolbar=no,directories=no,status=no,menubar=no,resizable=yes,modal=no' );    
setTimeout('WebSAGE.g_win_cmd.close()',500);
},

applyTableStyle: function()
{
var almFilter={};

// assemble the filterout list
WebSAGE.g_filterOutList = [];
for ( var substorpri in WebSAGE.g_alminfo ) 
   {
     if ( WebSAGE.g_alminfo.hasOwnProperty(substorpri) ) 
        {
        almFilter[substorpri] = WebSAGE.g_alminfo[substorpri].filterout;

        if ( WebSAGE.isAlarmsViewer() )
           {
           if (  WebSAGE.g_alminfo[substorpri].filterout )
              {
              WebSAGE.g_filterOutList.push(substorpri);
              $( '#DIV_' + WebSAGE.g_alminfo[substorpri].filterId ).css('opacity', '.25');
              }
           else
              {
              $( '#DIV_' + WebSAGE.g_alminfo[substorpri].filterId ).css('opacity', '1' );         
              }              
           }
        }
   }

if (storageAvailable('localStorage'))
  localStorage.setItem("almFilter",  JSON.stringify(almFilter));

// reapply line styles
for( var i = 0; i < L.length; i++ )
   {
   WebSAGE.EstiloLinha( i, 0 );   
   }
},

// Mostra os eventos recebidos
showVals: function()
{
  var dbg = 0;
  
  try
    {
    var qualif, i, cntsb, cntpr, xleft, ytop;
    var tam = L.length;  
    var sb;
    
    // quando houver o que mostrar e não for modo de todas anormalidades, mostra controles de filtro anormais e comandáveis
    if ( tam > 0 )
    if ( !WebSAGE.isAlarmsViewer() )
    if ( document.getElementById("OPC_CMDANORM").style.display === "none" )   
      {
        document.getElementById("OPC_CMDANORM").style.display = "inline";    
      }

    clearTimeout( WebSAGE.g_toutID ); // para de chamar o servidor
    
    var chkcmd = document.getElementById("cbMostraCmd").checked;
    var chknor = document.getElementById("cbForaNormal").checked;
    
    dbg = 1;
    
    // zeroes the alarm count and minimum priority
    for ( var subst in WebSAGE.g_alminfo ) 
     {
     if ( WebSAGE.g_alminfo.hasOwnProperty(subst) ) 
        {
        WebSAGE.g_alminfo[subst].countack = 0;
        WebSAGE.g_alminfo[subst].countnack = 0;
        WebSAGE.g_alminfo[subst].minpriorack = 10;
        WebSAGE.g_alminfo[subst].minpriornack = 10;
        }
     }

    // só mostra se vier uma linhas diferente da que já havia
    for( i = 0; i < tam; i++ )
      {
      if ( i >= WebSAGE.g_tbl.getRowsNum() )
        {
        dbg = 1.6; // bug intermitente do chrome

        WebSAGE.g_tbl.addRow( i, L[i] );
        if ( WebSAGE.g_subst_list.indexOf( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_SUBEST ) ) == -1 )
          {
          WebSAGE.g_subst_list.push( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_SUBEST ) );
          }
        WebSAGE.EstiloLinha( i, 0 );
        }
      else
        {
        dbg = 1.1; // bug intermitente do chrome

        if ( L[i] != WebSAGE.g_tbl.getUserData(i) ) // enquanto for igual, mantém
           {
           WebSAGE.g_tbl.changeRow( i, L[i] );
           if ( WebSAGE.g_subst_list.indexOf( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_SUBEST ) ) == -1 )
             {
             WebSAGE.g_subst_list.push( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_SUBEST ) );
             }
           WebSAGE.EstiloLinha( i, 0 );
           }  
        }
     
     // account alarms by priority and substation, leave out commands
     if ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("J") == -1 )
       {      
       var pri = WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).charAt(0);
       // count alarms by priority
       if ( typeof( WebSAGE.g_alminfo[ pri ] ) === 'undefined' )
         {
         WebSAGE.g_alminfo[ pri ] =  { countnack: 0, 
                                       countack: 0, 
                                       filterout: false, 
                                       minpriorack: 10, 
                                       minpriornack: 10, 
                                       is_subst: false };
         }
       if ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("L") != -1 )
         {
           WebSAGE.g_alminfo[ pri ].countnack += 1;
           WebSAGE.g_alminfo[ pri ].minpriornack = pri;
         }  
       else
         {
           WebSAGE.g_alminfo[ pri ].countack += 1;
           WebSAGE.g_alminfo[ pri ].minpriorack = pri;
         }         

       // count alarms by substation
       sb = WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_SUBEST );
       if ( typeof( WebSAGE.g_alminfo[ sb ] ) === 'undefined' )
         {
         WebSAGE.g_alminfo[ sb ] =  { countnack: 0, 
                                      countack: 0, 
                                      filterout: false, 
                                      minpriorack: 10, 
                                      minpriornack: 10, 
                                      is_subst: true };
         }
       // count and verify minimum priority of unacknowledged and acknowledge alarms
       if ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("L") != -1 )
         {
           WebSAGE.g_alminfo[ sb ].countnack += 1;
           if ( pri < WebSAGE.g_alminfo[ sb ].minpriornack )
             WebSAGE.g_alminfo[ sb ].minpriornack = pri;
         }
       else  
         {
           WebSAGE.g_alminfo[ sb ].countack += 1;
           if ( pri < WebSAGE.g_alminfo[ sb ].minpriorack )
             WebSAGE.g_alminfo[ sb ].minpriorack = pri;
         }
       }  
     }

    cntpr = -1; 
    cntsb = -1; 
    if ( WebSAGE.isAlarmsViewer() ) // manage alarm boxes
    for ( var substorpri in WebSAGE.g_alminfo ) 
      {
      if ( WebSAGE.g_alminfo.hasOwnProperty(substorpri) ) 
        {
        if ( WebSAGE.g_alminfo[substorpri].is_subst )
          {
            ytop = 42;
            xleft = 5;
            cntsb++;
          }
        else
          {
            ytop = 2;
            xleft = 230 + 68;
            cntpr++;
          }            

        if ( document.getElementById( "DIV_" + substorpri ) === null )
          { // create priority and substation alm boxes
          if ( WebSAGE.g_alminfo[substorpri].is_subst )
            { // create substation filters inside a div
            ytop=2;
            //$( "#DIV_SUBSTALMFILT" ).prepend(
            //"<div id='DIV_" + substorpri + "' " +
            //   " onclick='WebSAGE.g_alminfo[&quot;" + substorpri + "&quot;].filterout = ! WebSAGE.g_alminfo[&quot;" + substorpri + "&quot;].filterout;WebSAGE.applyTableStyle();'" +
            //   " style='position:relative;float:left;text-align:center;margin-bottom:7px;margin-left:3px;top:" + ytop + "px;" + 
            //           // "left:" + ( xleft + ( WebSAGE.g_alminfo[substorpri].is_subst ? cntsb : cntpr ) * 68 ) + "px;" + 
            //           "cursor:pointer;box-shadow: 1px 1px 1px #666666;background-color:white;min-width:48px;height:20px;border-radius:4px;border:1px solid #777;padding-top:2px;padding-left:2px;padding-right:2px;line-height:80%;font-size:15px;font-family:trebuchet ms,tahoma,helvetica,arial'>"+ 
            //  substorpri + 
            //  "<br>" + 
            //  "<span id='SP_" + substorpri +  "_ACK" +
            //      "' style='float:right;-webkit-filter:contrast(.7);text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;'>" + 
            //  "</span>" + 
            //  "<span id='SP_" + substorpri + "_NACK" +
            //      "' style='float:right;text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;'>" + 
            //  "</span>" + 
            //"</div>" );
            }
            else
            {
            ytop = 5;
            $( "body" ).append(
               "<div id='DIV_" + substorpri + "' " +
               " onclick='WebSAGE.g_alminfo[&quot;" + substorpri + "&quot;].filterout = ! WebSAGE.g_alminfo[&quot;" + substorpri + "&quot;].filterout;WebSAGE.applyTableStyle();'" +
               " style='position:absolute;float:left;text-align:center;top:" + ytop + "px;" + 
                        "left:" + ( xleft + substorpri * 68 ) + "px;" + 
                       "cursor:pointer;box-shadow: 1px 1px 1px #666666;background-color:white;min-width:48px;height:20px;border-radius:4px;border:1px solid #777;padding-top:2px;padding-left:2px;padding-right:2px;line-height:80%;font-size:15px;font-family:trebuchet ms,tahoma,helvetica,arial'>"+ 
              substorpri + 
              "<br>" + 
              "<span id='SP_" + substorpri +  "_ACK" +
                  "' style='float:right;-webkit-filter:contrast(.7);text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;background-color:silver;border-color:silver;'>" + 
              "0</span>" + 
              "<span id='SP_" + substorpri + "_NACK" +
                  "' style='float:right;text-align:center;font-size:12px;border-radius:15px;border:2px solid;margin:1px;background-color:silver;border-color:silver;'>" + 
              "0</span>" + 
              "</div>" );              
            }
          }
          
        // update alarm boxes state/counts  
        $( '#DIV_' + substorpri ).css( 'opacity', WebSAGE.g_alminfo[substorpri].filterout ? '.25' : '1' );         
        $( '#SP_' + substorpri + "_NACK").text( WebSAGE.g_alminfo[substorpri].countnack );
        $( '#SP_' + substorpri + "_NACK" ).css( 'background-color', ColorOfPriority[ WebSAGE.g_alminfo[substorpri].minpriornack ] );
        $( '#SP_' + substorpri + "_NACK" ).css( 'border-color',  ColorOfPriority[ WebSAGE.g_alminfo[substorpri].minpriornack ] );
        $( '#SP_' + substorpri + "_ACK").text(  WebSAGE.g_alminfo[substorpri].countack );
        $( '#SP_' + substorpri + "_ACK" ).css( 'background-color', ColorOfPriority[ WebSAGE.g_alminfo[substorpri].minpriorack ] );
        $( '#SP_' + substorpri + "_ACK" ).css( 'border-color',  ColorOfPriority[ WebSAGE.g_alminfo[substorpri].minpriorack ] );
        }      
      }
    
    while ( tam < WebSAGE.g_tbl.getRowsNum() ) // para caso onde ficam entrando e saindo linhas (qdo mostra tudo anormal)) 
     {
        WebSAGE.g_tbl.delRow( WebSAGE.g_tbl.getRowsNum() - 1 );
     }

    /*
    // se a janela info estiver aberta, fica atualizando o ponto 
    if ( WebSAGE.g_win_cmd )
    if ( WebSAGE.g_win_cmd.window )
    if ( typeof(WebSAGE.g_win_cmd.window) == 'object' )
    if ( typeof(WebSAGE.g_win_cmd.window.closed) != 'undefined' )
       if ( ! WebSAGE.g_win_cmd.window.closed )
        {
        WebSAGE.getRealtimeData()  
        clearTimeout( WebSAGE.g_timerID );
        WebSAGE.g_timerID = setTimeout( WebSAGE.showValsInfo2, 100 );
        }
    */

    if ( WebSAGE.g_muda_mod == 1 )      
      setTimeout( WebSAGE.mostraMods, 100 ); // atualiza opções de módulo
    
    var dtFmt = "";
    try{
      dtFmt = (new Date()).toLocaleString(TabularViewer_LocaleDateTime, TabularViewer_LocaleDateTimeOptions);
    }
    catch(e){
      dtFmt = (new Date()).toLocaleString();
    }
    $('#ATUALIZACAO').text( dtFmt );
    $('#NUMREGS').text( WebSAGE.g_tbl.getRowsNum() + " Tot - " );
  
    if ( gup("SOCMD") == 1 && document.getElementById("cbMostraCmd").checked != true && WebSAGE.g_firstdraw )
      document.getElementById("cbMostraCmd").click();
    
    if ( WebSAGE.g_firstdraw == 1 )
      WebSAGE.g_firstdraw = 0;  

    Ventoinha.pulse(".");
    }
catch (e)
    {
    // o CHROME gera exceptions aqui de vez em quando!
    //

    Ventoinha.pulse("E");    
    }    

  // próximo refresh
  WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh );
},
  
// falha todos os dados caso servidor pare de atualizar por um tempo  
falhaTudo: function()
{
  WebSAGE.g_tbl.clearAll();
  WebSAGE.g_tbl.addRow( 0, ",,," + Msg.FalhaWebserver + ",,,,", 0 );
  L=[];
  WebSAGE.g_waitingServer = 0;
  
  // vai tentar de novo
  clearTimeout( WebSAGE.g_toutID );
  WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, WebSAGE.g_timeOutRefresh );
},

doSilenceBeep: function() 
{
    WebSAGE.ackAlarm(-1);
},

EstiloLinha: function(id, rec)
{
try
  {
      var stl = '';
      id = parseInt(id);
      var pri = parseInt( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).charAt(0) );

      // alinha os analógicos à direita
      if (  $.isNumeric(WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_EVENTO )) )
          {
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_EVENTO].style.textAlign = "right";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_EVENTO].style.paddingRight = "35px";
          }
      else
          {
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_EVENTO].style.textAlign = "left";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_EVENTO].style.paddingRight = "0px";
          }

      // esconde os filtrados
      if ( WebSAGE.g_filterOutList.indexOf( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_SUBEST ) ) != -1  ||
           WebSAGE.g_filterOutList.indexOf( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).charAt(0) ) != -1
         )
          {
            WebSAGE.g_tbl.setRowHidden( id, true );
            return;
          }
        
      // esconde os comandos  
      if ( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("J") != -1 )
          {
            WebSAGE.g_tbl.setRowHidden( id, true );
            return;
          }

      // testa filtro pelos comandáveis
      var chk = document.getElementById("cbMostraCmd").checked;
      if (  WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_SUPCMD ) == 0 && chk )
          {
            WebSAGE.g_tbl.setRowHidden( id, true );
            return;
          }

      // testa filtro pelos anormais
      chk = document.getElementById("cbForaNormal").checked;
      if ( !( parseInt( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_FLAGS ) ) & 0x800 ) && chk )
          {
            WebSAGE.g_tbl.setRowHidden( id, true );
            return;
          }

      if ( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("L") == -1 || rec )
          { // reconhecido
          // Se alarme inibido, mostra texto mais claro
          if  ( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("I") != -1 )
            stl = 'color: ' + chroma(TabularViewer_AckTxtColor).brighten().brighten() + ';';
          else
            stl = 'color: ' + TabularViewer_AckTxtColor + ';';                        
          }
      else    
          { // não reconhecido (alarmado)
              if ( pri === 0 )
                {
                stl = 'color: ' + ColorOfPriority[ pri ] + ';';
                }
              else  
                {
                stl = 'color: ' + TabularViewer_AlmTxtColor + ';';
                }                
          }

      if ( pri === 0 )
          {
          WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_DESCR].style.fontWeight = "bold";
          WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_EVENTO].style.fontWeight = "bold";
          }
        else 
          {
          WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_DESCR].style.fontWeight = "inherit";
          WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_EVENTO].style.fontWeight = "inherit";
          }		

      if ( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("L") != -1 ||
           WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("P") != -1
         )
         {
            // let the qualifiers columns be in the color of priority
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.backgroundColor = 
              ColorOfPriority[ pri ];
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.borderRadius = "10px";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.color = "black";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.textAlign = "center";
            
            if ( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("L") != -1 )
               WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.opacity = "1";
            else   
               WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.opacity = ".3";
         }
      else
         {         
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.backgroundColor = "";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.borderRadius = "";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.color = "";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.textAlign = "center";
            WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.opacity = "1";
         }
          
      WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_DATA].style.paddingLeft = "5px";
      WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_DATA_CAMPO].style.paddingLeft = "5px";
      
      if ( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_QUALIF ).indexOf("F") != -1 )
          { // falhado
            stl = 'color: ' + TabularViewer_FailTxtColor + ';';
          }

      WebSAGE.g_tbl.setRowTextStyle( id, stl );

      WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_DATA].style.fontSize = "smaller";
      WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_DATA_CAMPO].style.fontSize = "smaller";
      WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_QUALIF].style.fontSize = "smaller";

      if ( WebSAGE.isAlarmsViewer() )
        {
        // differentiate the substation name
        WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_SUBEST].style.paddingLeft = 
          ( WebSAGE.g_subst_list.indexOf( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_SUBEST ) ) * 5 ) % 30 + "px"
        WebSAGE.g_tbl.rows[id+1].cells[WebSAGE.g_COL_SUBEST].style.color = 
          ColorOfSubstation[ WebSAGE.g_subst_list.indexOf( WebSAGE.g_tbl.cellsx( id, WebSAGE.g_COL_SUBEST ) ) % ColorOfSubstation.length ];
        }
  }
catch ( e ) 
  { 
  document.getElementById("VENTOINHA").title = e.stack;
  }        
},

ackAllAlarms: function(rec_apaga)
{
    WebSAGE.ackAlarm(0, 0, 0);
    // clearTimeout(WebSAGE.g_toutID);
	  // WebSAGE.g_toutID = setTimeout(WebSAGE.callServer, WebSAGE.g_timeOutReconhece);
},

// linha selecionada: reconhece  
doOnRowSelected: function( evt )
{
    var rec = false;
    var row = 0;
    
    if ( evt === 0 )
      {
        row = 0;
        rec = true;
      }
    else  
      {
        row = evt.currentTarget.rowIndex - 1 ;
        if ( WebSAGE.isAlarmsViewer() )
          rec = true;
        if ( evt.ctrlKey )
          rec = true;
        if ( evt.altKey || evt.shiftKey )
          rec = false;
      }

    if ( evt.which == 2 )
      rec = true;    

    var Qualif = WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_QUALIF );    
    
    // RECONHECE ALARME, SE PRESSIONOU CONTROL
    if ( rec )
      {

      if ( WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_FLAGS) & 0x100 )
        {
        WebSAGE.ackAlarm(0, 0, WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_NPONTO ) );

        // tira o L do qualificador          
        WebSAGE.g_tbl.cellsSetValue( row, WebSAGE.g_COL_QUALIF, Qualif.replace("L","") );
        WebSAGE.EstiloLinha( row, 1 );
        }          
      }          
    else          
      {
      // somente silencia alarme
      if ( WebSAGE.g_tbl.cellsx( row, WebSAGE.g_COL_FLAGS) & 0x100 )
          WebSAGE.doSilenceBeep();
      WebSAGE.janelaInfo( WebSAGE.g_tbl.cellsx(row, WebSAGE.g_COL_NPONTO) );
      } 
},

mostraTodosAnormais: function()
{
  // testa se realmente deseja sair
  window.onbeforeunload = function() { return Msg.ConfirmaSaida; };

  WebSAGE.g_timeOutRefresh = WebSAGE.g_timeOutRefresh / 1.5; // shorten refresh time for alarms viewer
  document.getElementById('FiltroSE').value = WebSAGE.g_filt_all_alarms;
  WebSAGE.g_titulo_janela = Msg.NomeVisorAnormais + " - " +  Msg.NomeProduto + " - " + Msg.VersaoProduto + getUsernameFmt();
  document.title = "."; // necessário devido a um bug do chromium!
  document.title = WebSAGE.g_titulo_janela;
  
  LoadFavicon( Imgs.FAVICONANORM );
  WebSAGE.mudaFiltro();
  document.getElementById("cbMostraCmd").checked = false;
  document.getElementById("cbForaNormal").checked = false;
  document.getElementById("OPC_CMDANORM").style.display = "none";
  document.getElementById("OPC_FILTROS").style.display = "none";
  
  // document.getElementById("IMGTABULAR").style.display = "none";
  $('#IMGTABULAR').attr('src', Imgs["ANORM_ID"] );
  $('#IMGTABULAR').attr('title', "" );
  
  document.getElementById("imgReconheceTudo").style.display = "";
},

// filter by alarm condition (unacknowledged + persistent)
mostraAlarmados: function()
{
    // pára atualização 
    clearTimeout( WebSAGE.g_toutID );

    var chknor = document.getElementById("cbForaNormal").checked;
    
    for( var i in L )
      {
        var flg = parseInt( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_FLAGS ) );
        
        if ( ( !( flg & 0x900 ) // 0x800=unack.alarm 0x100=persistent alarm
             && 
             chknor 
             )
            || ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_QUALIF ).indexOf("J") != -1 )  // comando
           )
          WebSAGE.g_tbl.setRowHidden( i, true );
        else
          WebSAGE.g_tbl.setRowHidden( i, false );
      }

    if ( chknor )  // enquanto estiver selecionado, segue filtrando
       {
       WebSAGE.g_bloqc = 1; // bloqueia para evitar refiltro pelo comando
       document.getElementById("cbMostraCmd").checked = false;
       }

   // atualiza após 2s
   WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, 2000 );
},
      
mostraCmd: function()
{
    // pára atualização 
    clearTimeout( WebSAGE.g_toutID );

    var chk = document.getElementById("cbMostraCmd").checked;
    
    for( var i in L )
      {
      if ( ( WebSAGE.g_tbl.cellsx(i,WebSAGE.g_COL_QUALIF).indexOf("J") != -1 )  // comando
             || ( WebSAGE.g_tbl.cellsx( i, WebSAGE.g_COL_SUPCMD ) == 0 && chk ) 
         )
        WebSAGE.g_tbl.setRowHidden( i, true );
      else
        WebSAGE.g_tbl.setRowHidden( i, false );		  
      }

    if ( chk ) 
       {
       WebSAGE.g_bloqa = 1; // bloqueia para evitar refiltro pelo estado anormal
       document.getElementById("cbForaNormal").checked = false;
       }
   
   // atualiza após 2s
   WebSAGE.g_toutID = setTimeout( WebSAGE.callServer, 2000 );
},
  
fontSize: function(updn)
{
    if ( updn == 1 && WebSAGE.g_fontsize < 30 ) 
      WebSAGE.g_fontsize = parseInt(WebSAGE.g_fontsize) + 1;
    else   
    if ( updn == 0 && WebSAGE.g_fontsize > 16 ) 
      WebSAGE.g_fontsize = parseInt(WebSAGE.g_fontsize) - 1;
    else
      return;  

    // escreve tamanho da fonte em cookie
    var date = new Date();
    date.setTime( date.getTime() + (3000 * 24 * 60 * 60 * 1000) );
    document.cookie = 'tab_fontsize=' + WebSAGE.g_fontsize + '; expires=' + date.toGMTString();
    
    window.onbeforeunload = null;

    // recarrega a página
    window.location.reload();        
},

tbl_prepare: function()
{
    WebSAGE.g_tbl = document.getElementById('TBLEVE');
    WebSAGE.g_tblhead = document.getElementById('TBLHEAD');

    WebSAGE.g_tbl.setColAlign =
        function( vals ) 
        {  
        };

    WebSAGE.g_tbl.setInitWidths =
        function( vals ) 
        {  
        WebSAGE.g_tbl.larguraCol = [];
        var varr = vals.split(',');
        for (var i=0; i < varr.length; i++)
           {
           WebSAGE.g_tbl.larguraCol[i] = varr[i];
           }
        };

    WebSAGE.g_tbl.setHeader = 
        function( vals ) 
        {  
        var varr = vals.split(',');

        if ( WebSAGE.g_tbl.rows.length == 0 )
            WebSAGE.g_tbl.insertRow( 0 );

        for (var i=0; i < varr.length; i++)
        {
           WebSAGE.g_tbl.rows[0].insertCell(i);
           WebSAGE.g_tbl.rows[0].cells[i].innerHTML=varr[i];

           WebSAGE.g_tblhead.rows[0].insertCell(i);
           WebSAGE.g_tblhead.rows[0].cells[i].innerHTML=varr[i];

           if ( WebSAGE.g_tbl.larguraCol[i] == 0 )
              {
              WebSAGE.g_tbl.rows[0].cells[i].style.display = 'none';
              WebSAGE.g_tblhead.rows[0].cells[i].style.display = 'none';
              }
            WebSAGE.g_tbl.setColWidth(i, WebSAGE.g_tbl.larguraCol[i] );
        } 

        while ( WebSAGE.g_tbl.rows[0].cells.length > varr.length )
            WebSAGE.g_tbl.rows[0].deleteCell(WebSAGE.g_tbl.rows[0].cells.length-1);

        // acerta tamanho dos campos se a fonte for maior que a padrão
        if (WebSAGE.g_fontsize>16)
            {
            for ( i=0; i < varr.length; i++ )
                WebSAGE.g_tbl.setColWidth( i, (WebSAGE.g_tbl.getColWidth(i) * WebSAGE.g_fontsize)/16);
            }
            
        WebSAGE.g_tbl.rows[0].style.display = 'none';
        };

    WebSAGE.g_tbl.addRow = 
        function( line, vals ) 
        {  
        line++;	
        var varr = vals.split(WebSAGE.g_split_line_code);	
        WebSAGE.g_tbl.insertRow( line );
        WebSAGE.g_tbl.rows[line].onclick = WebSAGE.doOnRowSelected;
        WebSAGE.g_tbl.rows[line].ondblclick = WebSAGE.doOnRowSelected;
                    
        for (var i=0; i < varr.length; i++)
           {
           WebSAGE.g_tbl.rows[line].insertCell(i);
           WebSAGE.g_tbl.rows[line].cells[i].innerHTML = varr[i];
           WebSAGE.g_tbl.rows[line].cells[i].style.boxSizing = "border-box";
           WebSAGE.g_tbl.rows[line].cells[i].style.borderBottom = '1px solid ' + TabularViewer_GridColor;
           WebSAGE.g_tbl.rows[line].cells[i].style.padding = "1px 0px";
           if ( WebSAGE.g_tbl.larguraCol[i] == 0 )
             WebSAGE.g_tbl.rows[line].cells[i].style.display = 'none';
          else
             {
             WebSAGE.g_tbl.rows[line].cells[i].width = WebSAGE.g_tbl.larguraCol[i];  
             WebSAGE.g_tbl.rows[line].cells[i].style.minWidth = WebSAGE.g_tbl.larguraCol[i] + "px";  
             WebSAGE.g_tbl.rows[line].cells[i].style.textOverflow = "ellipsis";  
             }
           } 
        WebSAGE.g_tbl.rows[line].userData = vals;
        };

    WebSAGE.g_tbl.changeRow = 
        function( line, vals ) 
        {  
        line++;
        var varr = vals.split(WebSAGE.g_split_line_code);
        
        // equalizes number of columns
        while ( varr.length > WebSAGE.g_tbl.rows[line].cells.length )             
           {
           var cell = WebSAGE.g_tbl.rows[line].insertCell( -1 );
           var col = WebSAGE.g_tbl.rows[line].cells.length - 1;
           cell.style.borderBottom = '1px solid ' + TabularViewer_GridColor;
           if ( WebSAGE.g_tbl.larguraCol[col] == 0 )
             cell.style.display = 'none';
           else
             cell.width = WebSAGE.g_tbl.larguraCol[col];  
           }
         
        for ( var i = 0; i < varr.length; i++ )
           {
           WebSAGE.g_tbl.rows[line].cells[i].innerHTML = varr[i];
           } 
        WebSAGE.g_tbl.rows[line].userData = vals;
        };

    WebSAGE.g_tbl.copyRowContent = 
        function( linefrom, lineto ) 
        {
        linefrom++;	lineto++;
        for (var i=0; i < WebSAGE.g_tbl.rows[0].cells.length; i++)
          {
          WebSAGE.g_tbl.rows[lineto].cells[i].innerHTML=WebSAGE.g_tbl.rows[linefrom].cells[i].innerHTML;
          } 
        WebSAGE.g_tbl.rows[lineto].userData=WebSAGE.g_tbl.rows[linefrom].userData;
        };

    WebSAGE.g_tbl.getUserData = 
        function( line ) 
        {
            line++;
            return WebSAGE.g_tbl.rows[line].userData;
        };

    WebSAGE.g_tbl.getRowsNum = 
        function( ) 
        {
            return WebSAGE.g_tbl.rows.length-1;
        };

    WebSAGE.g_tbl.getColsNum = 
        function( ) 
        {
            return WebSAGE.g_tblhead.rows[0].cells.length;
        };

    WebSAGE.g_tbl.delRow = 
        function( row ) 
        {
            row++;
            return WebSAGE.g_tbl.deleteRow(row);
        };

    WebSAGE.g_tbl.setRowColor = 
        function( row, color ) 
        {
            row++;
            WebSAGE.g_tbl.rows[row].style.color = color;
        };

    WebSAGE.g_tbl.setRowHidden = 
        function( row, hide ) 
        {
            row++;
            if ( hide )
              WebSAGE.g_tbl.rows[row].style.display = 'none';
            else  
              WebSAGE.g_tbl.rows[row].style.display = '';
        };

    WebSAGE.g_tbl.cellsx = 
        function( row, col ) 
        {
            row++;
            return WebSAGE.g_tbl.rows[row].cells[col].innerHTML;
        };      
      
    WebSAGE.g_tbl.cellsSetValue = 
        function( row, col, value ) 
        {
            row++;
            return WebSAGE.g_tbl.rows[row].cells[col].innerHTML=value;
        };      

    WebSAGE.g_tbl.getColWidth = 
        function( col ) 
        {
            return WebSAGE.g_tbl.larguraCol[col];
        };

    WebSAGE.g_tbl.setColWidth = 
        function( col, width ) 
        {
            WebSAGE.g_tbl.larguraCol[col] = width;
            try { // can cause error in IE
              WebSAGE.g_tblhead.rows[0].cells[col].width = width;
            } catch (e) { 
              WebSAGE.g_tblhead.rows[0].cells[col].width = width + 1;
            }			
            WebSAGE.g_tblhead.rows[0].cells[col].style.minWidth = width + "px";
            WebSAGE.g_tblhead.rows[0].cells[col].style.display = (width==0)?'none':'';
            for (var i=0; i < WebSAGE.g_tbl.rows.length; i++) 
              {
                try { // can cause error in IE
                  WebSAGE.g_tbl.rows[i].cells[col].width = width;
                } catch (e) { 
                  WebSAGE.g_tbl.rows[i].cells[col].width = width + 1;
                }			
                WebSAGE.g_tbl.rows[i].cells[col].style.minWidth = width + "px";
                WebSAGE.g_tbl.rows[i].cells[col].style.display = (width==0)?'none':'';
              }
        return 0;
        };

    WebSAGE.g_tbl.setStyle = 
        function( ss_header, ss_grid, ss_selCell, ss_selRow ) 
        {
            WebSAGE.g_tbl.style.cssText = ss_grid;
            return 0;
        };

    WebSAGE.g_tbl.setRowTextStyle = 
        function( row, stl ) 
        {
            row++;
            WebSAGE.g_tbl.rows[row].style.cssText = stl;
            return 0;
        };

    WebSAGE.g_tbl.setCellTextStyle = 
        function( row, col, stl ) 
        {
            row++;
            WebSAGE.g_tbl.rows[row].cells[col].style.cssText = stl;
            return 0;
        };
        
    WebSAGE.g_tbl.clearAll = 
        function( ) 
        {
            while (WebSAGE.g_tbl.rows.length>1) 
            WebSAGE.g_tbl.deleteRow(1);
        };      
},

showInfo: function()
{
    var wid=WebSAGE.g_tbl.getColWidth(WebSAGE.g_COL_NPONTO);
    if ( wid == 0 )
      {
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_NPONTO, 60*WebSAGE.g_fontsize/16 );
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_ID, 230*WebSAGE.g_fontsize/16 );    
      WebSAGE.g_tbl.witdh='1500px';
      WebSAGE.g_tblhead.witdh='1500px';
      }
    else  
      {
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_NPONTO, 0 );
      WebSAGE.g_tbl.setColWidth( WebSAGE.g_COL_ID, 0 );    
      WebSAGE.g_tbl.witdh='1200px';
      WebSAGE.g_tblhead.witdh='1200px';
      }
},

init: function()
{
    WebSAGE.tbl_prepare();

    WebSAGE.g_titulo_janela = Msg.NomeVisorTabular + " - " +  Msg.NomeProduto + " - " + Msg.VersaoProduto + getUsernameFmt();
    document.title = "."; // necessário devido a um bug do chromium!
    document.title = WebSAGE.g_titulo_janela;

    // vai nos objetos com 'id' e coloca como 'title' a mensagem correspondente de Titles, carrega as imagens (de images.js)        
    $('img[id]').attr('src', function(index) { return Imgs[this.id]; } );
    $('img[id]').attr('title', function(index) { return Titles[this.id]; } );
    $('span[id]').attr('title', function(index) { return Titles[this.id]; } );
    $('input[id]').attr('title', function(index) { return Titles[this.id]; } );
    $('select[id]').attr('title', function(index) { return Titles[this.id]; } );
    $('#SPCOMANDAVEIS').text( Msg.SPCOMANDAVEIS );
    $('#SPANORMAIS').text( Msg.SPANORMAIS );
    $('#SPSUBEST').text( Msg.SPSUBEST );
    $('#SPMODULO').text( Msg.SPMODULO );
    $('#SPFILTROID').text( Msg.SPFILTROID );
    $('#TOOLBAR_ID').css('background-color', TabularViewer_ToolbarColor);
    $('#TOOLBAR_ID').css('display', ''); // shows toolbar

    // WebSAGE.g_tbl.setColAlign("center,left,center,left,center,right,right,center,center,center");

    // busca tamanho da fonte em cookie
    var fs = readCookie( "tab_fontsize" );
    if ( isInt ( fs ) )
      WebSAGE.g_fontsize = parseInt( fs );

    if ( WebSAGE.g_fontsize <= 16 )
      document.getElementById("imgFontSizeDown").style.display = "none";
    if ( WebSAGE.g_fontsize >= 30 )
      document.getElementById("imgFontSizeUp").style.display = "none";
      
    var gridstyle = "outline:none;cursor:pointer;font-weight:normal;border:0px;white;padding:0px;margin-left:5px;";
    gridstyle = gridstyle + 'table-layout:fixed;';
    gridstyle = gridstyle + 'color:' + TabularViewer_AckTxtColor + ';'; 
    gridstyle = gridstyle + 'font-family:' + TabularViewer_Font + ';'; 
    WebSAGE.g_tbl.setStyle(';', 'height:auto;font-size:' + WebSAGE.g_fontsize + 'px;' + gridstyle,';',';');
    WebSAGE.g_tbl.setInitWidths("0,0,70,400,180,0,0,55,0,170,170");
    WebSAGE.g_tbl.setHeader(Msg.TabNomesColunas);

    $('#gridbox').css('background-color', TabularViewer_TableColor);
    WebSAGE.g_tbl.cellSpacing = "0px";
    WebSAGE.g_tbl.cellPadding = "0px";
    WebSAGE.g_tblhead.cellSpacing = "0px";
    WebSAGE.g_tblhead.cellPadding = "0px";
    $('#TBLHEAD').css('margin-left', '5px');
    $('#TBLHEAD').css('table-layout', 'fixed');
    $('#TBLHEAD').css('background-color', 'gainsboro');
    document.body.bgColor = 'gainsboro';
    $('#TBLHEAD').css('color', 'black');
    $('#TBLHEAD').css('font-family', TabularViewer_Font);

    shortcut.add( "F8", // reconhece tudo
                  function() { WebSAGE.ackAllAlarms(0); },
                  { 'type':'keydown', 'propagate':true,	'target':document } );
    shortcut.add( "F9",
                  function() { WebSAGE.doSilenceBeep(); },
                  { 'type':'keydown', 'propagate':true, 'target':document } );
    shortcut.add( "1",
                  function() { document.getElementById("cbMostraCmd").click(); },
                  { 'type':'keydown', 'propagate':true, 'disable_in_input':true, 'target':document } );
    shortcut.add( "2",
                  function() { document.getElementById("cbForaNormal").click(); },
                  { 'type':'keydown', 'propagate':true, 'disable_in_input':true, 'target':document } );
                  
    // teclas de atalho para tamanho da fonte
    shortcut.add( "", // Num[+] : Aumenta
                  function() { WebSAGE.fontSize(1); },
                  { 'type':'keydown', 'propagate':false, 'target':document, 'keycode':107 } );
    shortcut.add( "", // Num[-] : Diminnui
                  function() { WebSAGE.fontSize(0); },
                  { 'type':'keydown', 'propagate':false, 'target':document, 'keycode':109 } );

    // atalhos para percorrer a lista de pontos
    shortcut.add( "home", 
                  function() { document.getElementById('TBLEVE').focus(); },
                  {'type':'keydown', 'propagate':true,	'target':document} );
    shortcut.add( "end", 
                  function() { document.getElementById('TBLEVE').focus(); },
                  {'type':'keydown', 'propagate':true,	'target':document} );    
    shortcut.add( "pagedown", 
                  function() { document.getElementById('TBLEVE').focus(); },
                  {'type':'keydown', 'propagate':true,	'target':document} );
    shortcut.add( "pageup", 
                  function() { document.getElementById('TBLEVE').focus(); },
                  {'type':'keydown', 'propagate':true,	'target':document} );    
    shortcut.add( "down", // desc12e sem numlock
                  function() { document.getElementById('TBLEVE').focus(); },
                  {'type':'keydown', 'propagate':true,	'target':document} );
    shortcut.add( "up", // sobe sem numlock
                  function() { document.getElementById('TBLEVE').focus(); },
                  {'type':'keydown', 'propagate':true,	'target':document} );

    shortcut.add( "esc",
                  function() { if ( typeof(WebSAGE.g_win_cmd.window) == 'object' ) // fecha janela info
                               if ( WebSAGE.g_win_cmd.window ) 
                                 WebSAGE.g_win_cmd.window.close();                                 
                           },
                  {'type':'keydown', 'propagate':false, 'target':document} );           
                  
    // dispara ventoinha
    Ventoinha.init("VENTOINHA"); 
    Ventoinha.periodico();

    setTimeout( WebSAGE.getGroup1List, 500 );

    if ( WebSAGE.isAlarmsViewer() )
      {
      }
    else    
      {
      LoadFavicon( Imgs.FAVICONTABULAR );      
      }
      
    document.getElementById('TBLEVE').tabIndex=1; // permite foco na tabela
    document.getElementById('TBLEVE').focus();      

    // desabilita o botão direito 
    document.oncontextmenu = function() { return false; };

    // alarm beep sound
    if (WebSAGE.isAlarmsViewer())
      document.getElementById('btSound').style.display='';
    document.getElementById('btSound').addEventListener("click", function () {      
        if (document.getElementById('btSound').value === '🔊'){
          document.getElementById('btSound').value = '🔈';
          clearInterval(window.sndIntervalHandle);
        } else {
          document.getElementById('nonCriticalSound').play();
          document.getElementById('btSound').value = '🔊';
          window.sndIntervalHandle = setInterval( function(){        
		  if (window.WebSAGE.getValue(BEEP_POINTKEY))
            switch (ALARMBEEP_TYPE) {
              default:
                document.getElementById('nonCriticalSound').play();
                break;
              case 2:
                document.getElementById('criticalSound').play();
                break;                 
            }
          }, 1500);
        }
      });
    }
};
Core.start(WebSAGE);

</script>
</head>
<body style='margin:0px; overflow:hidden; user-select:none;'>
<div id='DIV_TOOL' style='position:fixed;top:0px;left:0px;width:100%;box-shadow: 2px 2px 2px #888888;'>
<table id="TOOLBAR_ID" style='display:none;font-family:tahoma;font-size:16px;' height='8%' width='100%' cellpadding='0' cellspacing='0'>
<tr height='40px'>
<td style='vertical-align:middle;height:36px;white-space:nowrap;'>&nbsp;
<img id='IMGTABULAR' alt='' src='' align='middle' width='32' height='32' onclick='WebSAGE.showInfo()' style='cursor:pointer;'/>
&nbsp;&nbsp;&nbsp;&nbsp;
<img id="imgFontSizeUp" alt='' src='' align='middle' width="27" height="27" onclick='WebSAGE.fontSize(1)' style='cursor:pointer;' />
<img id="imgFontSizeDown" alt='' src='' align='middle' width="27" height="27" onclick='WebSAGE.fontSize(0)' style='cursor:pointer;' />
<img id='imgReconheceTudo' alt='' src='' align='middle' width="32" height="32" onclick='WebSAGE.ackAllAlarms(0);' style='cursor:pointer;display:none' />
<img id='imgClipboard' alt='' src='' onclick='CopyClipboard("TBLEVE")' align='middle' border='0' width='32' height='32' style='cursor:pointer;' />
<input id="btSound" title="Beep ON/OFF" style="display:none;cursor:pointer;vertical-align:middle;background:none;border:0px;font-size:20px;transform: translateY(3px);" type="button" value="🔈" />&nbsp;
<audio id="criticalSound"><source src="sounds/critical.wav" muted type="audio/wav"></audio>
<audio id="nonCriticalSound"><source src="sounds/nonCritical.wav" muted type="audio/wav"></audio>
<img id='SILENCIA_ID' alt='' src='' align='middle' width="32" height="32" onclick='document.getElementById("SILENCIA_ID").style.display="none";WebSAGE.doSilenceBeep();' style='display:none; cursor:pointer;' />
&nbsp;&nbsp;&nbsp;&nbsp;
<div id='OPC_CMDANORM' style='display:none' >
<input id="cbMostraCmd" style='vertical-align:middle;' type="checkbox" onclick='WebSAGE.mostraCmd()' /><span id='SPCOMANDAVEIS'>?</span>&nbsp;
<input id="cbForaNormal" style='vertical-align:middle;' type="checkbox" onclick='WebSAGE.mostraAlarmados()' /><span id='SPANORMAIS'>?</span>&nbsp;
</div>

<div style="position: absolute; right: 0px; top: 0px; ">
<small><span id="NUMREGS"style="font-weight:bold;" > </span> <span id='ATUALIZACAO'> </span></small>&nbsp;&nbsp;
<span id='VENTOINHA' style='font-family:courier;font-weight:bold;'>(!)</span>
</div>

</td>
</tr>
<tr>
<td style='vertical-align:middle;height:36px;white-space:nowrap;' >
<div id='OPC_FILTROS'> 
&nbsp;<span id='SPSUBEST'></span>:<select name="SELSE" id="SELSE" style='text-shadow: 1px 1px 1px #808080;' onfocus='this.onchange=WebSAGE.mudaSE;' size="1"></select>
<span id='SPMODULO'></span>:<select name="SELMOD" id="SELMOD" style='text-shadow: 1px 1px 1px #808080;' onchange='WebSAGE.mudaMod()' SIZE="1"></select>&nbsp;&nbsp;
&nbsp;<span id='SPFILTROID' style='display:none;'></span>
<input id="FiltroSE" style='display:none;text-shadow: 1px 1px 1px #808080;' onblur="WebSAGE.mudaFiltro();" onkeypress="if (event.witch==13 || event.keyCode==13) document.getElementById('SELSE').focus();" />
<input id="FiltroMod" style='display:none;text-shadow: 1px 1px 1px #808080;' onblur="WebSAGE.mudaFiltro();" onkeypress="if (event.witch==13 || event.keyCode==13) document.getElementById('SELSE').focus();" />
<input id="cbBloqAtu" onclick='WebSAGE.callServer()' type="checkbox" style="visibility:hidden;" />
</div>
</td>
</tr>
</table>
</div>
<div id="divhead" style="position:fixed;width:100%;top:80px;height:18px;">
<table id="TBLHEAD">
<tr><td></td></tr>
</table>
</div>
<div id="gridbox" style="position:fixed;width:100%;top:100px;height:calc(100% - 80px - 18px);overflow-y:auto;">
<table id="TBLEVE">
</table>
</div>
</body>
</html>
